From 50e813746702a27954eeb700e5b43ebf3e1d8244 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 28 Nov 2023 15:11:41 +0000 Subject: [PATCH 1/7] Add representation of storage-owning `Var` nodes (#10944) * Add representation of storage-owning `Var` nodes This adds the representation of `expr.Var` nodes that own their own storage locations, and consequently are not backed by existing Qiskit objects (`Clbit` or `ClassicalRegister`). This is the base of the ability for Qiskit to represent manual classical-value storage in `QuantumCircuit`, and the base for how manual storage will be implemented. * Minor documentation tweaks Co-authored-by: Matthew Treinish --------- Co-authored-by: Matthew Treinish --- qiskit/circuit/classical/expr/__init__.py | 15 +++++-- qiskit/circuit/classical/expr/expr.py | 43 ++++++++++++++++--- .../circuit/classical/test_expr_properties.py | 42 ++++++++++++++++++ 3 files changed, 92 insertions(+), 8 deletions(-) diff --git a/qiskit/circuit/classical/expr/__init__.py b/qiskit/circuit/classical/expr/__init__.py index d2cd4bc5044e..b2b4d138fca7 100644 --- a/qiskit/circuit/classical/expr/__init__.py +++ b/qiskit/circuit/classical/expr/__init__.py @@ -39,10 +39,11 @@ These objects are mutable and should not be reused in a different location without a copy. -The entry point from general circuit objects to the expression system is by wrapping the object -in a :class:`Var` node and associating a :class:`~.types.Type` with it. +The base for dynamic variables is the :class:`Var`, which can be either an arbitrarily typed runtime +variable, or a wrapper around a :class:`.Clbit` or :class:`.ClassicalRegister`. .. autoclass:: Var + :members: var, name Similarly, literals used in comparison (such as integers) should be lifted to :class:`Value` nodes with associated types. @@ -86,10 +87,18 @@ The functions and methods described in this section are a more user-friendly way to build the expression tree, while staying close to the internal representation. All these functions will automatically lift valid Python scalar values into corresponding :class:`Var` or :class:`Value` -objects, and will resolve any required implicit casts on your behalf. +objects, and will resolve any required implicit casts on your behalf. If you want to directly use +some scalar value as an :class:`Expr` node, you can manually :func:`lift` it yourself. .. autofunction:: lift +Typically you should create memory-owning :class:`Var` instances by using the +:meth:`.QuantumCircuit.add_var` method to declare them in some circuit context, since a +:class:`.QuantumCircuit` will not accept an :class:`Expr` that contains variables that are not +already declared in it, since it needs to know how to allocate the storage and how the variable will +be initialized. However, should you want to do this manually, you should use the low-level +:meth:`Var.new` call to safely generate a named variable for usage. + You can manually specify casts in cases where the cast is allowed in explicit form, but may be lossy (such as the cast of a higher precision :class:`~.types.Uint` to a lower precision one). diff --git a/qiskit/circuit/classical/expr/expr.py b/qiskit/circuit/classical/expr/expr.py index b9e9aad4a2b7..3adbacfd6926 100644 --- a/qiskit/circuit/classical/expr/expr.py +++ b/qiskit/circuit/classical/expr/expr.py @@ -31,6 +31,7 @@ import abc import enum import typing +import uuid from .. import types @@ -108,24 +109,56 @@ def __repr__(self): @typing.final class Var(Expr): - """A classical variable.""" + """A classical variable. - __slots__ = ("var",) + These variables take two forms: a new-style variable that owns its storage location and has an + associated name; and an old-style variable that wraps a :class:`.Clbit` or + :class:`.ClassicalRegister` instance that is owned by some containing circuit. In general, + construction of variables for use in programs should use :meth:`Var.new` or + :meth:`.QuantumCircuit.add_var`.""" + + __slots__ = ("var", "name") def __init__( - self, var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister, type: types.Type + self, + var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister | uuid.UUID, + type: types.Type, + *, + name: str | None = None, ): self.type = type self.var = var + """A reference to the backing data storage of the :class:`Var` instance. When lifting + old-style :class:`.Clbit` or :class:`.ClassicalRegister` instances into a :class:`Var`, + this is exactly the :class:`.Clbit` or :class:`.ClassicalRegister`. If the variable is a + new-style classical variable (one that owns its own storage separate to the old + :class:`.Clbit`/:class:`.ClassicalRegister` model), this field will be a :class:`~uuid.UUID` + to uniquely identify it.""" + self.name = name + """The name of the variable. This is required to exist if the backing :attr:`var` attribute + is a :class:`~uuid.UUID`, i.e. if it is a new-style variable, and must be ``None`` if it is + an old-style variable.""" + + @classmethod + def new(cls, name: str, type: types.Type) -> typing.Self: + """Generate a new named variable that owns its own backing storage.""" + return cls(uuid.uuid4(), type, name=name) def accept(self, visitor, /): return visitor.visit_var(self) def __eq__(self, other): - return isinstance(other, Var) and self.type == other.type and self.var == other.var + return ( + isinstance(other, Var) + and self.type == other.type + and self.var == other.var + and self.name == other.name + ) def __repr__(self): - return f"Var({self.var}, {self.type})" + if self.name is None: + return f"Var({self.var}, {self.type})" + return f"Var({self.var}, {self.type}, name='{self.name}')" @typing.final diff --git a/test/python/circuit/classical/test_expr_properties.py b/test/python/circuit/classical/test_expr_properties.py index a6873153c0eb..efda6ba37758 100644 --- a/test/python/circuit/classical/test_expr_properties.py +++ b/test/python/circuit/classical/test_expr_properties.py @@ -56,3 +56,45 @@ def test_expr_can_be_cloned(self, obj): self.assertEqual(obj, copy.copy(obj)) self.assertEqual(obj, copy.deepcopy(obj)) self.assertEqual(obj, pickle.loads(pickle.dumps(obj))) + + def test_var_equality(self): + """Test that various types of :class:`.expr.Var` equality work as expected both in equal and + unequal cases.""" + var_a_bool = expr.Var.new("a", types.Bool()) + self.assertEqual(var_a_bool, var_a_bool) + + # Allocating a new variable should not compare equal, despite the name match. A semantic + # equality checker can choose to key these variables on only their names and types, if it + # knows that that check is valid within the semantic context. + self.assertNotEqual(var_a_bool, expr.Var.new("a", types.Bool())) + + # Manually constructing the same object with the same UUID should cause it compare equal, + # though, for serialisation ease. + self.assertEqual(var_a_bool, expr.Var(var_a_bool.var, types.Bool(), name="a")) + + # This is a badly constructed variable because it's using a different type to refer to the + # same storage location (the UUID) as another variable. It is an IR error to generate this + # sort of thing, but we can't fully be responsible for that and a pass would need to go out + # of its way to do this incorrectly, but we can still ensure that the direct equality check + # would spot the error. + self.assertNotEqual( + var_a_bool, expr.Var(var_a_bool.var, types.Uint(8), name=var_a_bool.name) + ) + + # This is also badly constructed because it uses a different name to refer to the "same" + # storage location. + self.assertNotEqual(var_a_bool, expr.Var(var_a_bool.var, types.Bool(), name="b")) + + # Obviously, two variables of different types and names should compare unequal. + self.assertNotEqual(expr.Var.new("a", types.Bool()), expr.Var.new("b", types.Uint(8))) + # As should two variables of the same name but different storage locations and types. + self.assertNotEqual(expr.Var.new("a", types.Bool()), expr.Var.new("a", types.Uint(8))) + + def test_var_uuid_clone(self): + """Test that :class:`.expr.Var` instances that have an associated UUID and name roundtrip + through pickle and copy operations to produce values that compare equal.""" + var_a_u8 = expr.Var.new("a", types.Uint(8)) + + self.assertEqual(var_a_u8, pickle.loads(pickle.dumps(var_a_u8))) + self.assertEqual(var_a_u8, copy.copy(var_a_u8)) + self.assertEqual(var_a_u8, copy.deepcopy(var_a_u8)) From 868599a8c4148104765f3dacd1a1e924eca2b3b8 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Tue, 28 Nov 2023 19:23:28 +0100 Subject: [PATCH 2/7] update docs link in the README file to new home (#11334) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 739a03ca4e8e..a153957cb221 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ It also contains a transpiler that supports optimizing quantum circuits and a qu For more details on how to use Qiskit, refer to the documentation located here: - + ## Installation From 2cd5bbda39da32c778c8a9cd58b54967b5d7500a Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Tue, 28 Nov 2023 19:12:48 -0500 Subject: [PATCH 3/7] Fix Sabre extended set population order (#10651) * Visit runs of 1Q gates before next 2Q. * Avoid allocation in loop. --- crates/accelerate/src/sabre_swap/mod.rs | 36 ++++++++++++++++--------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/crates/accelerate/src/sabre_swap/mod.rs b/crates/accelerate/src/sabre_swap/mod.rs index ef5ee9e35a0a..2d4dc40481e1 100644 --- a/crates/accelerate/src/sabre_swap/mod.rs +++ b/crates/accelerate/src/sabre_swap/mod.rs @@ -171,23 +171,35 @@ fn populate_extended_set( let mut decremented: IndexMap = IndexMap::with_hasher(ahash::RandomState::default()); let mut i = 0; + let mut visit_now: Vec = Vec::new(); while i < to_visit.len() && extended_set.len() < EXTENDED_SET_SIZE { - for edge in dag.dag.edges_directed(to_visit[i], Direction::Outgoing) { - let successor_node = edge.target(); - let successor_index = successor_node.index(); - *decremented.entry(successor_index).or_insert(0) += 1; - required_predecessors[successor_index] -= 1; - if required_predecessors[successor_index] == 0 { - if !dag.dag[successor_node].directive - && !dag.node_blocks.contains_key(&successor_index) - { - if let [a, b] = dag.dag[successor_node].qubits[..] { - extended_set.push([a.to_phys(layout), b.to_phys(layout)]); + // Visit runs of non-2Q gates fully before moving on to children + // of 2Q gates. This way, traversal order is a BFS of 2Q gates rather + // than of all gates. + visit_now.push(to_visit[i]); + let mut j = 0; + while let Some(node) = visit_now.get(j) { + for edge in dag.dag.edges_directed(*node, Direction::Outgoing) { + let successor_node = edge.target(); + let successor_index = successor_node.index(); + *decremented.entry(successor_index).or_insert(0) += 1; + required_predecessors[successor_index] -= 1; + if required_predecessors[successor_index] == 0 { + if !dag.dag[successor_node].directive + && !dag.node_blocks.contains_key(&successor_index) + { + if let [a, b] = dag.dag[successor_node].qubits[..] { + extended_set.push([a.to_phys(layout), b.to_phys(layout)]); + to_visit.push(successor_node); + continue; + } } + visit_now.push(successor_node); } - to_visit.push(successor_node); } + j += 1; } + visit_now.clear(); i += 1; } for (node, amount) in decremented.iter() { From c3a7d409323b67dcb66c8e9c7af2b8e33cf8b928 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 28 Nov 2023 20:42:55 -0500 Subject: [PATCH 4/7] Update DAGCircuit.draw() docstring with current requirements (#11337) * Update DAGCircuit.draw() docstring with current requirements Since #8162 the dag drawer hasn't required pydot to run. It now uses rustworkx's graphviz_draw() function which directly calls graphviz. However, in #8162 the docstring for the DAGCircuit.draw() method was not updated to reflect this and the method documentation still said that pydot was required. This commit fixes this oversight and updates the docstring to correctly state that only graphviz is required (as rustworkx is a hard dependency for Qiskit it's not anything that needs to be documented). It also includes more details on how to install graphviz as this is often a potential source of confusion for users. * Update qiskit/dagcircuit/dagcircuit.py Co-authored-by: Jake Lishman --------- Co-authored-by: Jake Lishman --- qiskit/dagcircuit/dagcircuit.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 126f19f1bb5d..21e1464d06ed 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -2082,8 +2082,12 @@ def draw(self, scale=0.7, filename=None, style="color"): """ Draws the dag circuit. - This function needs `pydot `_, which in turn needs - `Graphviz `_ to be installed. + This function needs `Graphviz `_ to be + installed. Graphviz is not a python package and can't be pip installed + (the ``graphviz`` package on PyPI is a Python interface library for + Graphviz and does not actually install Graphviz). You can refer to + `the Graphviz documentation `__ on + how to install it. Args: scale (float): scaling factor From b72033e1cd2d37eaeaadde0c23e6ea76e44f7c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <99898527+grossardt@users.noreply.github.com> Date: Wed, 29 Nov 2023 09:26:03 +0100 Subject: [PATCH 5/7] Add pow as supported operation for ParameterExpression (#11235) * Add pow for Parameter Adds powering for qiskit.circuit.ParameterExpression allowing to use a**n, n**a, a**b, pow(a,b) etc. for ParameterExpressions a, b and numbers n. Minimal change using default support of pow by Sympy/Symengine. Added pow to list of operators in TestParameterExpressions test case for unit testing. fixes #8959 Changelog: New Feature * Update test_parameters.py --- qiskit/circuit/parameterexpression.py | 6 ++++ .../add-parameter-pow-ff5f8d10813f5733.yaml | 7 ++++ test/python/circuit/test_parameters.py | 35 +++++++++++++++++-- 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/add-parameter-pow-ff5f8d10813f5733.yaml diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 790fc29b8135..237d6e9d8007 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -341,6 +341,12 @@ def __truediv__(self, other): def __rtruediv__(self, other): return self._apply_operation(operator.truediv, other, reflected=True) + def __pow__(self, other): + return self._apply_operation(pow, other) + + def __rpow__(self, other): + return self._apply_operation(pow, other, reflected=True) + def _call(self, ufunc): return ParameterExpression(self._parameter_symbols, ufunc(self._symbol_expr)) diff --git a/releasenotes/notes/add-parameter-pow-ff5f8d10813f5733.yaml b/releasenotes/notes/add-parameter-pow-ff5f8d10813f5733.yaml new file mode 100644 index 000000000000..094266071990 --- /dev/null +++ b/releasenotes/notes/add-parameter-pow-ff5f8d10813f5733.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + :class:`~qiskit.circuit.ParameterExpression` (and thus also + :class:`~qiskit.circuit.Parameter`) now support powering: :code:`x**y` + where :code:`x` and :code:`y` can be any combination of + :class:`~qiskit.circuit.ParameterExpression` and number types. diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index c694b39e6e7a..6a5f780a96af 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -1332,7 +1332,14 @@ def _paramvec_names(prefix, length): class TestParameterExpressions(QiskitTestCase): """Test expressions of Parameters.""" - supported_operations = [add, sub, mul, truediv] + # supported operations dictionary operation : accuracy (0=exact match) + supported_operations = { + add: 0, + sub: 0, + mul: 0, + truediv: 0, + pow: 1e-12, + } def setUp(self): super().setUp() @@ -1504,12 +1511,19 @@ def test_expressions_of_parameter_with_constant(self): x = Parameter("x") - for op in self.supported_operations: + for op, rel_tol in self.supported_operations.items(): for const in good_constants: expr = op(const, x) bound_expr = expr.bind({x: 2.3}) - self.assertEqual(complex(bound_expr), op(const, 2.3)) + res = complex(bound_expr) + expected = op(const, 2.3) + if rel_tol > 0: + self.assertTrue( + cmath.isclose(res, expected, rel_tol=rel_tol), f"{res} != {expected}" + ) + else: + self.assertEqual(res, expected) # Division by zero will raise. Tested elsewhere. if const == 0 and op == truediv: @@ -1954,6 +1968,21 @@ def test_parameter_expression_grad(self): self.assertEqual(expr.gradient(x), 2 * x) self.assertEqual(expr.gradient(x).gradient(x), 2) + def test_parameter_expression_exp_log_vs_pow(self): + """Test exp, log, pow for ParameterExpressions by asserting x**y = exp(y log(x)).""" + + x = Parameter("x") + y = Parameter("y") + pow1 = x**y + pow2 = (y * x.log()).exp() + for x_val in [2, 1.3, numpy.pi]: + for y_val in [2, 1.3, 0, -1, -1.0, numpy.pi, 1j]: + with self.subTest(msg="with x={x_val}, y={y_val}"): + vals = {x: x_val, y: y_val} + pow1_val = pow1.bind(vals) + pow2_val = pow2.bind(vals) + self.assertTrue(cmath.isclose(pow1_val, pow2_val), f"{pow1_val} != {pow2_val}") + def test_bound_expression_is_real(self): """Test is_real on bound parameters.""" x = Parameter("x") From 2e640b2bae84bc775659a715eb64a6de274111a6 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 29 Nov 2023 03:26:22 -0500 Subject: [PATCH 6/7] Remove legacy qasm2 parser (#11308) * Remove legacy qasm2 parser This commit removes the legacy qasm2 parser. This was the original qasm parser in qiskit dating back to the very beginning of the project, but it has been superseded in recent releases by the faster more correct/strict rust parser. We no longer need to keep around two parsers for qasm2. This commit removes the legacy one and it's associated functions (like the ast converter). * Remove api docs too * Remove ast converter benchmark * Remove QuantumCircuit.qasm() exporter method This commit removes the QuantumCircuit.qasm() method as part of the legacy parser removal. The exporter method was tied to some of the internals in the now removed `qiskit.qasm` module. The qasm() method was already queued up for eventual removal, this commit just pushes that forward so we don't have to add more comptaibility code into it to keep it working as is without the qiskit.qasm module existing anymore. * Remove jupyter qasm widget * Update tests * Remove BlueprintCircuit .qasm() override * Fix rst syntax in release note * Apply suggestions from code review Co-authored-by: Jake Lishman --------- Co-authored-by: Jake Lishman --- docs/apidoc/index.rst | 1 - docs/apidoc/qasm.rst | 6 - qiskit/circuit/library/blueprintcircuit.py | 5 - qiskit/circuit/quantumcircuit.py | 60 - qiskit/converters/__init__.py | 2 - qiskit/converters/ast_to_dag.py | 418 ------ qiskit/qasm/__init__.py | 53 - qiskit/qasm/exceptions.py | 16 - qiskit/qasm/node/__init__.py | 41 - qiskit/qasm/node/barrier.py | 30 - qiskit/qasm/node/binaryop.py | 59 - qiskit/qasm/node/binaryoperator.py | 52 - qiskit/qasm/node/cnot.py | 31 - qiskit/qasm/node/creg.py | 45 - qiskit/qasm/node/customunitary.py | 49 - qiskit/qasm/node/expressionlist.py | 33 - qiskit/qasm/node/external.py | 87 -- qiskit/qasm/node/format.py | 37 - qiskit/qasm/node/gate.py | 62 - qiskit/qasm/node/gatebody.py | 41 - qiskit/qasm/node/id.py | 78 -- qiskit/qasm/node/idlist.py | 33 - qiskit/qasm/node/if_.py | 39 - qiskit/qasm/node/indexedid.py | 41 - qiskit/qasm/node/intnode.py | 51 - qiskit/qasm/node/measure.py | 30 - qiskit/qasm/node/node.py | 59 - qiskit/qasm/node/nodeexception.py | 26 - qiskit/qasm/node/opaque.py | 58 - qiskit/qasm/node/prefix.py | 54 - qiskit/qasm/node/primarylist.py | 33 - qiskit/qasm/node/program.py | 32 - qiskit/qasm/node/qreg.py | 45 - qiskit/qasm/node/real.py | 63 - qiskit/qasm/node/reset.py | 29 - qiskit/qasm/node/unaryoperator.py | 49 - qiskit/qasm/node/universalunitary.py | 32 - qiskit/qasm/pygments/__init__.py | 34 - qiskit/qasm/pygments/lexer.py | 133 -- qiskit/qasm/qasm.py | 53 - qiskit/qasm/qasmlexer.py | 203 --- qiskit/qasm/qasmparser.py | 1156 ----------------- qiskit/qasm2/__init__.py | 13 - qiskit/tools/jupyter/library.py | 66 +- ...-legacy-qasm2-parser-53ad3f1817fd68cc.yaml | 34 + requirements-optional.txt | 1 - requirements.txt | 1 - setup.py | 1 - test/benchmarks/converters.py | 5 - test/python/basicaer/test_qasm_simulator.py | 3 +- .../circuit/library/test_permutation.py | 5 +- test/python/circuit/test_circuit_qasm.py | 160 +-- test/python/circuit/test_circuit_registers.py | 3 +- test/python/circuit/test_unitary.py | 23 +- test/python/compiler/test_compiler.py | 5 +- test/python/converters/test_ast_to_dag.py | 67 - test/python/qasm2/test_circuit_methods.py | 24 +- test/python/qasm2/test_legacy_importer.py | 508 -------- test/python/test_qasm_parser.py | 126 -- 59 files changed, 141 insertions(+), 4363 deletions(-) delete mode 100644 docs/apidoc/qasm.rst delete mode 100644 qiskit/converters/ast_to_dag.py delete mode 100644 qiskit/qasm/__init__.py delete mode 100644 qiskit/qasm/exceptions.py delete mode 100644 qiskit/qasm/node/__init__.py delete mode 100644 qiskit/qasm/node/barrier.py delete mode 100644 qiskit/qasm/node/binaryop.py delete mode 100644 qiskit/qasm/node/binaryoperator.py delete mode 100644 qiskit/qasm/node/cnot.py delete mode 100644 qiskit/qasm/node/creg.py delete mode 100644 qiskit/qasm/node/customunitary.py delete mode 100644 qiskit/qasm/node/expressionlist.py delete mode 100644 qiskit/qasm/node/external.py delete mode 100644 qiskit/qasm/node/format.py delete mode 100644 qiskit/qasm/node/gate.py delete mode 100644 qiskit/qasm/node/gatebody.py delete mode 100644 qiskit/qasm/node/id.py delete mode 100644 qiskit/qasm/node/idlist.py delete mode 100644 qiskit/qasm/node/if_.py delete mode 100644 qiskit/qasm/node/indexedid.py delete mode 100644 qiskit/qasm/node/intnode.py delete mode 100644 qiskit/qasm/node/measure.py delete mode 100644 qiskit/qasm/node/node.py delete mode 100644 qiskit/qasm/node/nodeexception.py delete mode 100644 qiskit/qasm/node/opaque.py delete mode 100644 qiskit/qasm/node/prefix.py delete mode 100644 qiskit/qasm/node/primarylist.py delete mode 100644 qiskit/qasm/node/program.py delete mode 100644 qiskit/qasm/node/qreg.py delete mode 100644 qiskit/qasm/node/real.py delete mode 100644 qiskit/qasm/node/reset.py delete mode 100644 qiskit/qasm/node/unaryoperator.py delete mode 100644 qiskit/qasm/node/universalunitary.py delete mode 100644 qiskit/qasm/pygments/__init__.py delete mode 100644 qiskit/qasm/pygments/lexer.py delete mode 100644 qiskit/qasm/qasm.py delete mode 100644 qiskit/qasm/qasmlexer.py delete mode 100644 qiskit/qasm/qasmparser.py create mode 100644 releasenotes/notes/remove-legacy-qasm2-parser-53ad3f1817fd68cc.yaml delete mode 100644 test/python/converters/test_ast_to_dag.py delete mode 100644 test/python/qasm2/test_legacy_importer.py delete mode 100644 test/python/test_qasm_parser.py diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst index eb5ed9e3c2c5..9d1eb2f64960 100644 --- a/docs/apidoc/index.rst +++ b/docs/apidoc/index.rst @@ -30,7 +30,6 @@ API Reference primitives qasm2 qasm3 - qasm qobj qpy quantum_info diff --git a/docs/apidoc/qasm.rst b/docs/apidoc/qasm.rst deleted file mode 100644 index c1fea25947bb..000000000000 --- a/docs/apidoc/qasm.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-qasm: - -.. automodule:: qiskit.qasm - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/qiskit/circuit/library/blueprintcircuit.py b/qiskit/circuit/library/blueprintcircuit.py index 51f6e5826bfc..4244a1f2340f 100644 --- a/qiskit/circuit/library/blueprintcircuit.py +++ b/qiskit/circuit/library/blueprintcircuit.py @@ -122,11 +122,6 @@ def parameters(self) -> ParameterView: self._build() return super().parameters - def qasm(self, formatted=False, filename=None, encoding=None): - if not self._is_built: - self._build() - return super().qasm(formatted, filename, encoding) - def _append(self, instruction, _qargs=None, _cargs=None): if not self._is_built: self._build() diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 4d406daf8189..cb164404ac1e 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -44,7 +44,6 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.parameter import Parameter from qiskit.circuit.exceptions import CircuitError -from qiskit.utils import optionals as _optionals from qiskit.utils.deprecation import deprecate_func from . import _classical_resource_map from ._utils import sort_parameters @@ -1619,65 +1618,6 @@ def decompose( # do not copy operations, this is done in the conversion with circuit_to_dag return dag_to_circuit(dag, copy_operations=False) - def qasm( - self, - formatted: bool = False, - filename: str | None = None, - encoding: str | None = None, - ) -> str | None: - """Return OpenQASM 2.0 string. - - .. seealso:: - - :func:`.qasm2.dump` and :func:`.qasm2.dumps` - The preferred entry points to the OpenQASM 2 export capabilities. These match the - interface for other serialisers in Qiskit. - - Args: - formatted (bool): Return formatted OpenQASM 2.0 string. - filename (str): Save OpenQASM 2.0 to file with name 'filename'. - encoding (str): Optionally specify the encoding to use for the - output file if ``filename`` is specified. By default this is - set to the system's default encoding (ie whatever - ``locale.getpreferredencoding()`` returns) and can be set to - any valid codec or alias from stdlib's - `codec module `__ - - Returns: - str: If formatted=False. - - Raises: - MissingOptionalLibraryError: If pygments is not installed and ``formatted`` is - ``True``. - QASM2ExportError: If circuit has free parameters. - QASM2ExportError: If an operation that has no OpenQASM 2 representation is encountered. - """ - from qiskit import qasm2 # pylint: disable=cyclic-import - - out = qasm2.dumps(self) - if filename is not None: - with open(filename, "w+", encoding=encoding) as file: - print(out, file=file) - - if formatted: - _optionals.HAS_PYGMENTS.require_now("formatted OpenQASM 2.0 output") - - import pygments - from pygments.formatters import ( # pylint: disable=no-name-in-module - Terminal256Formatter, - ) - from qiskit.qasm.pygments import OpenQASMLexer - from qiskit.qasm.pygments import QasmTerminalStyle - - code = pygments.highlight( - out, OpenQASMLexer(), Terminal256Formatter(style=QasmTerminalStyle) - ) - print(code) - return None - # The old `QuantumCircuit.qasm()` method included a terminating new line that `qasm2.dumps` - # doesn't, so for full compatibility we add it back here. - return out + "\n" - def draw( self, output: str | None = None, diff --git a/qiskit/converters/__init__.py b/qiskit/converters/__init__.py index afac4cd2231f..459b739ee011 100644 --- a/qiskit/converters/__init__.py +++ b/qiskit/converters/__init__.py @@ -21,7 +21,6 @@ .. autofunction:: dag_to_circuit .. autofunction:: circuit_to_instruction .. autofunction:: circuit_to_gate -.. autofunction:: ast_to_dag .. autofunction:: dagdependency_to_circuit .. autofunction:: circuit_to_dagdependency .. autofunction:: dag_to_dagdependency @@ -32,7 +31,6 @@ from .dag_to_circuit import dag_to_circuit from .circuit_to_instruction import circuit_to_instruction from .circuit_to_gate import circuit_to_gate -from .ast_to_dag import ast_to_dag from .circuit_to_dagdependency import circuit_to_dagdependency from .dagdependency_to_circuit import dagdependency_to_circuit from .dag_to_dagdependency import dag_to_dagdependency diff --git a/qiskit/converters/ast_to_dag.py b/qiskit/converters/ast_to_dag.py deleted file mode 100644 index 4cd60f56573c..000000000000 --- a/qiskit/converters/ast_to_dag.py +++ /dev/null @@ -1,418 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -AST (abstract syntax tree) to DAG (directed acyclic graph) converter. - -Acts as an OpenQASM interpreter. -""" -from collections import OrderedDict -from qiskit.dagcircuit import DAGCircuit -from qiskit.exceptions import QiskitError - -from qiskit.circuit import QuantumRegister, ClassicalRegister, Gate, QuantumCircuit -from qiskit.qasm.node.real import Real -from qiskit.circuit.measure import Measure -from qiskit.circuit.reset import Reset -from qiskit.circuit.barrier import Barrier -from qiskit.circuit.delay import Delay -from qiskit.circuit.library import standard_gates as std - - -def ast_to_dag(ast): - """Build a ``DAGCircuit`` object from an AST ``Node`` object. - - Args: - ast (Program): a Program Node of an AST (parser's output) - - Return: - DAGCircuit: the DAG representing an OpenQASM's AST - - Raises: - QiskitError: if the AST is malformed. - - Example: - .. code-block:: - - from qiskit.converters import ast_to_dag - from qiskit import qasm, QuantumCircuit, ClassicalRegister, QuantumRegister - - q = QuantumRegister(3, 'q') - c = ClassicalRegister(3, 'c') - circ = QuantumCircuit(q, c) - circ.h(q[0]) - circ.cx(q[0], q[1]) - circ.measure(q[0], c[0]) - circ.rz(0.5, q[1]).c_if(c, 2) - qasm_str = circ.qasm() - ast = qasm.Qasm(data=qasm_str).parse() - dag = ast_to_dag(ast) - """ - dag = DAGCircuit() - AstInterpreter(dag)._process_node(ast) - - return dag - - -class AstInterpreter: - """Interprets an OpenQASM by expanding subroutines and unrolling loops.""" - - standard_extension = { - "u1": std.U1Gate, - "u2": std.U2Gate, - "u3": std.U3Gate, - "u": std.UGate, - "p": std.PhaseGate, - "x": std.XGate, - "y": std.YGate, - "z": std.ZGate, - "t": std.TGate, - "tdg": std.TdgGate, - "s": std.SGate, - "sdg": std.SdgGate, - "sx": std.SXGate, - "sxdg": std.SXdgGate, - "swap": std.SwapGate, - "rx": std.RXGate, - "rxx": std.RXXGate, - "ry": std.RYGate, - "rz": std.RZGate, - "rzz": std.RZZGate, - "id": std.IGate, - "h": std.HGate, - "cx": std.CXGate, - "cy": std.CYGate, - "cz": std.CZGate, - "ch": std.CHGate, - "crx": std.CRXGate, - "cry": std.CRYGate, - "crz": std.CRZGate, - "csx": std.CSXGate, - "cu1": std.CU1Gate, - "cp": std.CPhaseGate, - "cu": std.CUGate, - "cu3": std.CU3Gate, - "ccx": std.CCXGate, - "cswap": std.CSwapGate, - "delay": Delay, - "rccx": std.RCCXGate, - "rc3x": std.RC3XGate, - "c3x": std.C3XGate, - "c3sqrtx": std.C3SXGate, - "c4x": std.C4XGate, - } - - def __init__(self, dag): - """Initialize interpreter's data.""" - # DAG object to populate - self.dag = dag - # OPENQASM version number (ignored for now) - self.version = 0.0 - # Dict of gates names and properties - self.gates = OrderedDict() - # Keeping track of conditional gates - self.condition = None - # List of dictionaries mapping local parameter ids to expression Nodes - self.arg_stack = [{}] - # List of dictionaries mapping local bit ids to global ids (name, idx) - self.bit_stack = [{}] - - def _process_bit_id(self, node): - """Process an Id or IndexedId node as a bit or register type. - - Return a list of tuples (Register,index). - """ - reg = None - - if node.name in self.dag.qregs: - reg = self.dag.qregs[node.name] - elif node.name in self.dag.cregs: - reg = self.dag.cregs[node.name] - else: - raise QiskitError( - "expected qreg or creg name:", "line=%s" % node.line, "file=%s" % node.file - ) - - if node.type == "indexed_id": - # An indexed bit or qubit - return [reg[node.index]] - elif node.type == "id": - # A qubit or qreg or creg - if not self.bit_stack[-1]: - # Global scope - return list(reg) - else: - # local scope - if node.name in self.bit_stack[-1]: - return [self.bit_stack[-1][node.name]] - raise QiskitError( - "expected local bit name:", "line=%s" % node.line, "file=%s" % node.file - ) - return None - - def _process_custom_unitary(self, node): - """Process a custom unitary node.""" - name = node.name - if node.arguments is not None: - args = self._process_node(node.arguments) - else: - args = [] - bits = [self._process_bit_id(node_element) for node_element in node.bitlist.children] - - if name in self.gates: - self._arguments(name, bits, args) - else: - raise QiskitError( - "internal error undefined gate:", "line=%s" % node.line, "file=%s" % node.file - ) - - def _process_u(self, node): - """Process a U gate node.""" - args = self._process_node(node.arguments) - bits = [self._process_bit_id(node.bitlist)] - - self._arguments("u", bits, args) - - def _arguments(self, name, bits, args): - gargs = self.gates[name]["args"] - gbits = self.gates[name]["bits"] - - maxidx = max(map(len, bits)) - for idx in range(maxidx): - self.arg_stack.append({gargs[j]: args[j] for j in range(len(gargs))}) - # Only index into register arguments. - element = [idx * x for x in [len(bits[j]) > 1 for j in range(len(bits))]] - self.bit_stack.append({gbits[j]: bits[j][element[j]] for j in range(len(gbits))}) - self._create_dag_op( - name, - [self.arg_stack[-1][s].sym() for s in gargs], - [self.bit_stack[-1][s] for s in gbits], - ) - self.arg_stack.pop() - self.bit_stack.pop() - - def _process_gate(self, node, opaque=False): - """Process a gate node. - - If opaque is True, process the node as an opaque gate node. - """ - self.gates[node.name] = {} - de_gate = self.gates[node.name] - de_gate["print"] = True # default - de_gate["opaque"] = opaque - de_gate["n_args"] = node.n_args() - de_gate["n_bits"] = node.n_bits() - if node.n_args() > 0: - de_gate["args"] = [element.name for element in node.arguments.children] - else: - de_gate["args"] = [] - de_gate["bits"] = [c.name for c in node.bitlist.children] - if node.name in self.standard_extension: - return - if opaque: - de_gate["body"] = None - else: - de_gate["body"] = node.body - - def _process_cnot(self, node): - """Process a CNOT gate node.""" - id0 = self._process_bit_id(node.children[0]) - id1 = self._process_bit_id(node.children[1]) - if not (len(id0) == len(id1) or len(id0) == 1 or len(id1) == 1): - raise QiskitError( - "internal error: qreg size mismatch", "line=%s" % node.line, "file=%s" % node.file - ) - maxidx = max([len(id0), len(id1)]) - for idx in range(maxidx): - cx_gate = std.CXGate() - if self.condition: - cx_gate = cx_gate.c_if(*self.condition) - if len(id0) > 1 and len(id1) > 1: - self.dag.apply_operation_back(cx_gate, [id0[idx], id1[idx]], [], check=False) - elif len(id0) > 1: - self.dag.apply_operation_back(cx_gate, [id0[idx], id1[0]], [], check=False) - else: - self.dag.apply_operation_back(cx_gate, [id0[0], id1[idx]], [], check=False) - - def _process_measure(self, node): - """Process a measurement node.""" - id0 = self._process_bit_id(node.children[0]) - id1 = self._process_bit_id(node.children[1]) - if len(id0) != len(id1): - raise QiskitError( - "internal error: reg size mismatch", "line=%s" % node.line, "file=%s" % node.file - ) - for idx, idy in zip(id0, id1): - meas_gate = Measure() - if self.condition: - meas_gate = meas_gate.c_if(*self.condition) - self.dag.apply_operation_back(meas_gate, [idx], [idy], check=False) - - def _process_if(self, node): - """Process an if node.""" - creg_name = node.children[0].name - creg = self.dag.cregs[creg_name] - cval = node.children[1].value - self.condition = (creg, cval) - self._process_node(node.children[2]) - self.condition = None - - def _process_children(self, node): - """Call process_node for all children of node.""" - for kid in node.children: - self._process_node(kid) - - def _process_node(self, node): - """Carry out the action associated with a node.""" - if node.type == "program": - self._process_children(node) - - elif node.type == "qreg": - qreg = QuantumRegister(node.index, node.name) - self.dag.add_qreg(qreg) - - elif node.type == "creg": - creg = ClassicalRegister(node.index, node.name) - self.dag.add_creg(creg) - - elif node.type == "id": - raise QiskitError("internal error: _process_node on id") - - elif node.type == "int": - raise QiskitError("internal error: _process_node on int") - - elif node.type == "real": - raise QiskitError("internal error: _process_node on real") - - elif node.type == "indexed_id": - raise QiskitError("internal error: _process_node on indexed_id") - - elif node.type == "id_list": - # We process id_list nodes when they are leaves of barriers. - return [self._process_bit_id(node_children) for node_children in node.children] - - elif node.type == "primary_list": - # We should only be called for a barrier. - return [self._process_bit_id(m) for m in node.children] - - elif node.type == "gate": - self._process_gate(node) - - elif node.type == "custom_unitary": - self._process_custom_unitary(node) - - elif node.type == "universal_unitary": - self._process_u(node) - - elif node.type == "cnot": - self._process_cnot(node) - - elif node.type == "expression_list": - return node.children - - elif node.type == "binop": - raise QiskitError("internal error: _process_node on binop") - - elif node.type == "prefix": - raise QiskitError("internal error: _process_node on prefix") - - elif node.type == "measure": - self._process_measure(node) - - elif node.type == "format": - self.version = node.version() - - elif node.type == "barrier": - ids = self._process_node(node.children[0]) - qubits = [] - for qubit in ids: - for j, _ in enumerate(qubit): - qubits.append(qubit[j]) - self.dag.apply_operation_back(Barrier(len(qubits)), qubits, [], check=False) - - elif node.type == "reset": - id0 = self._process_bit_id(node.children[0]) - for i, _ in enumerate(id0): - reset = Reset() - if self.condition: - reset = reset.c_if(*self.condition) - self.dag.apply_operation_back(reset, [id0[i]], [], check=False) - - elif node.type == "if": - self._process_if(node) - - elif node.type == "opaque": - self._process_gate(node, opaque=True) - - elif node.type == "external": - raise QiskitError("internal error: _process_node on external") - - else: - raise QiskitError( - "internal error: undefined node type", - node.type, - "line=%s" % node.line, - "file=%s" % node.file, - ) - return None - - def _gate_rules_to_qiskit_circuit(self, node, params): - """From a gate definition in OpenQASM, to a QuantumCircuit format.""" - rules = [] - qreg = QuantumRegister(node["n_bits"]) - bit_args = {node["bits"][i]: q for i, q in enumerate(qreg)} - exp_args = {node["args"][i]: Real(q) for i, q in enumerate(params)} - - for child_op in node["body"].children: - qparams = [] - eparams = [] - for param_list in child_op.children[1:]: - if param_list.type == "id_list": - qparams = [bit_args[param.name] for param in param_list.children] - elif param_list.type == "expression_list": - for param in param_list.children: - eparams.append(param.sym(nested_scope=[exp_args])) - op = self._create_op(child_op.name, params=eparams) - rules.append((op, qparams, [])) - circ = QuantumCircuit(qreg) - for instr, qargs, cargs in rules: - circ._append(instr, qargs, cargs) - return circ - - def _create_dag_op(self, name, params, qargs): - """ - Create a DAG node out of a parsed AST op node. - - Args: - name (str): operation name to apply to the DAG - params (list): op parameters - qargs (list(Qubit)): qubits to attach to - - Raises: - QiskitError: if encountering a non-basis opaque gate - """ - op = self._create_op(name, params) - if self.condition: - op = op.c_if(*self.condition) - self.dag.apply_operation_back(op, qargs, [], check=False) - - def _create_op(self, name, params): - if name in self.standard_extension: - op = self.standard_extension[name](*params) - elif name in self.gates: - op = Gate(name=name, num_qubits=self.gates[name]["n_bits"], params=params) - if not self.gates[name]["opaque"]: - # call a custom gate (otherwise, opaque) - op.definition = self._gate_rules_to_qiskit_circuit(self.gates[name], params=params) - else: - raise QiskitError("unknown operation for ast node name %s" % name) - return op diff --git a/qiskit/qasm/__init__.py b/qiskit/qasm/__init__.py deleted file mode 100644 index 322d230343c6..000000000000 --- a/qiskit/qasm/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -========================= -Qasm (:mod:`qiskit.qasm`) -========================= - -.. currentmodule:: qiskit.qasm - -QASM Routines -============= - -.. autoclass:: Qasm - - -Pygments -======== - -.. autoclass:: OpenQASMLexer - :class-doc-from: class - -.. autoclass:: QasmHTMLStyle - :class-doc-from: class - -.. autoclass:: QasmTerminalStyle - :class-doc-from: class -""" - -from numpy import pi - -from qiskit.utils.optionals import HAS_PYGMENTS - -from .qasm import Qasm -from .exceptions import QasmError - - -def __getattr__(name): - if name in ("OpenQASMLexer", "QasmHTMLStyle", "QasmTerminalStyle"): - import qiskit.qasm.pygments - - return getattr(qiskit.qasm.pygments, name) - - raise AttributeError(f"module '{__name__}' has no attribute '{name}'") diff --git a/qiskit/qasm/exceptions.py b/qiskit/qasm/exceptions.py deleted file mode 100644 index 7bc40db35b8b..000000000000 --- a/qiskit/qasm/exceptions.py +++ /dev/null @@ -1,16 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Exception for errors raised while handling OpenQASM 2.0.""" - -# Re-export from the new place to ensure that old code continues to work. -from qiskit.qasm2.exceptions import QASM2Error as QasmError # pylint: disable=unused-import diff --git a/qiskit/qasm/node/__init__.py b/qiskit/qasm/node/__init__.py deleted file mode 100644 index 09e24db01bf5..000000000000 --- a/qiskit/qasm/node/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""OpenQASM 2 nodes.""" - -from .barrier import Barrier -from .binaryop import BinaryOp -from .binaryoperator import BinaryOperator -from .cnot import Cnot -from .creg import Creg -from .customunitary import CustomUnitary -from .expressionlist import ExpressionList -from .external import External -from .gate import Gate -from .gatebody import GateBody -from .id import Id -from .idlist import IdList -from .if_ import If -from .indexedid import IndexedId -from .intnode import Int -from .format import Format -from .measure import Measure -from .opaque import Opaque -from .prefix import Prefix -from .primarylist import PrimaryList -from .program import Program -from .qreg import Qreg -from .real import Real -from .reset import Reset -from .unaryoperator import UnaryOperator -from .universalunitary import UniversalUnitary -from .nodeexception import NodeException diff --git a/qiskit/qasm/node/barrier.py b/qiskit/qasm/node/barrier.py deleted file mode 100644 index afa16f2f9222..000000000000 --- a/qiskit/qasm/node/barrier.py +++ /dev/null @@ -1,30 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM barrier statement.""" - -from .node import Node - - -class Barrier(Node): - """Node for an OPENQASM barrier statement. - - children[0] is a primarylist node. - """ - - def __init__(self, children): - """Create the barrier node.""" - super().__init__("barrier", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "barrier " + self.children[0].qasm() + ";" diff --git a/qiskit/qasm/node/binaryop.py b/qiskit/qasm/node/binaryop.py deleted file mode 100644 index 45d4de4364d0..000000000000 --- a/qiskit/qasm/node/binaryop.py +++ /dev/null @@ -1,59 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM binary operation expression.""" - -from qiskit.exceptions import MissingOptionalLibraryError -from .node import Node - - -class BinaryOp(Node): - """Node for an OPENQASM binary operation expression. - - children[0] is the operation, as a binary operator node. - children[1] is the left expression. - children[2] is the right expression. - """ - - def __init__(self, children): - """Create the binaryop node.""" - super().__init__("binop", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return ( - "(" + self.children[1].qasm() + self.children[0].value + self.children[2].qasm() + ")" - ) - - def latex(self): - """Return the corresponding math mode latex string.""" - try: - from pylatexenc.latexencode import utf8tolatex - except ImportError as ex: - raise MissingOptionalLibraryError( - "pylatexenc", "latex-from-qasm exporter", "pip install pylatexenc" - ) from ex - return utf8tolatex(self.sym()) - - def real(self): - """Return the correspond floating point number.""" - operation = self.children[0].operation() - lhs = self.children[1].real() - rhs = self.children[2].real() - return operation(lhs, rhs) - - def sym(self, nested_scope=None): - """Return the correspond symbolic number.""" - operation = self.children[0].operation() - lhs = self.children[1].sym(nested_scope) - rhs = self.children[2].sym(nested_scope) - return operation(lhs, rhs) diff --git a/qiskit/qasm/node/binaryoperator.py b/qiskit/qasm/node/binaryoperator.py deleted file mode 100644 index 57e7d883c547..000000000000 --- a/qiskit/qasm/node/binaryoperator.py +++ /dev/null @@ -1,52 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM binary operator.""" - -import operator - -from .node import Node -from .nodeexception import NodeException - - -VALID_OPERATORS = { - "+": operator.add, - "-": operator.sub, - "*": operator.mul, - "/": operator.truediv, - "^": operator.pow, -} - - -class BinaryOperator(Node): - """Node for an OPENQASM binary operator. - - This node has no children. The data is in the value field. - """ - - def __init__(self, operation): - """Create the operator node.""" - super().__init__("operator", None, None) - self.value = operation - - def operation(self): - """ - Return the operator as a function f(left, right). - """ - try: - return VALID_OPERATORS[self.value] - except KeyError as ex: - raise NodeException(f"internal error: undefined operator '{self.value}'") from ex - - def qasm(self): - """Return the OpenQASM 2 representation.""" - return self.value diff --git a/qiskit/qasm/node/cnot.py b/qiskit/qasm/node/cnot.py deleted file mode 100644 index 3034d0ca8e32..000000000000 --- a/qiskit/qasm/node/cnot.py +++ /dev/null @@ -1,31 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM CNOT statement.""" - -from .node import Node - - -class Cnot(Node): - """Node for an OPENQASM CNOT statement. - - children[0], children[1] are id nodes if CX is inside a gate body, - otherwise they are primary nodes. - """ - - def __init__(self, children): - """Create the cnot node.""" - super().__init__("cnot", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "CX " + self.children[0].qasm() + "," + self.children[1].qasm() + ";" diff --git a/qiskit/qasm/node/creg.py b/qiskit/qasm/node/creg.py deleted file mode 100644 index 6a6bac6cb400..000000000000 --- a/qiskit/qasm/node/creg.py +++ /dev/null @@ -1,45 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM creg statement.""" -from .node import Node - - -class Creg(Node): - """Node for an OPENQASM creg statement. - - children[0] is an indexedid node. - """ - - def __init__(self, children): - """Create the creg node.""" - super().__init__("creg", children, None) - # This is the indexed id, the full "id[n]" object - self.id = children[0] # pylint: disable=invalid-name - # Name of the creg - self.name = self.id.name - # Source line number - self.line = self.id.line - # Source file name - self.file = self.id.file - # Size of the register - self.index = self.id.index - - def to_string(self, indent): - """Print the node data, with indent.""" - ind = indent * " " - print(ind, "creg") - self.children[0].to_string(indent + 3) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "creg " + self.id.qasm() + ";" diff --git a/qiskit/qasm/node/customunitary.py b/qiskit/qasm/node/customunitary.py deleted file mode 100644 index 393cf160f3af..000000000000 --- a/qiskit/qasm/node/customunitary.py +++ /dev/null @@ -1,49 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM custom gate statement.""" -from .node import Node - - -class CustomUnitary(Node): - """Node for an OPENQASM custom gate statement. - - children[0] is an id node. - children[1] is an exp_list (if len==3) or primary_list. - children[2], if present, is a primary_list. - - Has properties: - .id = id node - .name = gate name string - .arguments = None or exp_list node - .bitlist = primary_list node - """ - - def __init__(self, children): - """Create the custom gate node.""" - super().__init__("custom_unitary", children, None) - self.id = children[0] # pylint: disable=invalid-name - self.name = self.id.name - if len(children) == 3: - self.arguments = children[1] - self.bitlist = children[2] - else: - self.arguments = None - self.bitlist = children[1] - - def qasm(self): - """Return the corresponding OPENQASM string.""" - string = self.name - if self.arguments is not None: - string += "(" + self.arguments.qasm() + ")" - string += " " + self.bitlist.qasm() + ";" - return string diff --git a/qiskit/qasm/node/expressionlist.py b/qiskit/qasm/node/expressionlist.py deleted file mode 100644 index bef4f7b2e268..000000000000 --- a/qiskit/qasm/node/expressionlist.py +++ /dev/null @@ -1,33 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM expression list.""" -from .node import Node - - -class ExpressionList(Node): - """Node for an OPENQASM expression list. - - children are expression nodes. - """ - - def __init__(self, children): - """Create the expression list node.""" - super().__init__("expression_list", children, None) - - def size(self): - """Return the number of expressions.""" - return len(self.children) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return ",".join([self.children[j].qasm() for j in range(self.size())]) diff --git a/qiskit/qasm/node/external.py b/qiskit/qasm/node/external.py deleted file mode 100644 index 2aecbf26bb62..000000000000 --- a/qiskit/qasm/node/external.py +++ /dev/null @@ -1,87 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM external function.""" - -import numpy as np - -from qiskit.exceptions import MissingOptionalLibraryError -from .node import Node -from .nodeexception import NodeException - - -class External(Node): - """Node for an OPENQASM external function. - - children[0] is an id node with the name of the function. - children[1] is an expression node. - """ - - def __init__(self, children): - """Create the external node.""" - super().__init__("external", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return self.children[0].qasm() + "(" + self.children[1].qasm() + ")" - - def latex(self): - """Return the corresponding math mode latex string.""" - try: - from pylatexenc.latexencode import utf8tolatex - except ImportError as ex: - raise MissingOptionalLibraryError( - "pylatexenc", "latex-from-qasm exporter", "pip install pylatexenc" - ) from ex - return utf8tolatex(self.sym()) - - def real(self, nested_scope=None): - """Return the correspond floating point number.""" - op = self.children[0].name - expr = self.children[1] - dispatch = { - "sin": np.sin, - "cos": np.cos, - "tan": np.tan, - "asin": np.arcsin, - "acos": np.arccos, - "atan": np.arctan, - "exp": np.exp, - "ln": np.log, - "sqrt": np.sqrt, - } - if op in dispatch: - arg = expr.real(nested_scope) - return dispatch[op](arg) - else: - raise NodeException("internal error: undefined external") - - def sym(self, nested_scope=None): - """Return the corresponding symbolic expression.""" - op = self.children[0].name - expr = self.children[1] - dispatch = { - "sin": np.sin, - "cos": np.cos, - "tan": np.tan, - "asin": np.arcsin, - "acos": np.arccos, - "atan": np.arctan, - "exp": np.exp, - "ln": np.log, - "sqrt": np.sqrt, - } - if op in dispatch: - arg = expr.sym(nested_scope) - return dispatch[op](arg) - else: - raise NodeException("internal error: undefined external") diff --git a/qiskit/qasm/node/format.py b/qiskit/qasm/node/format.py deleted file mode 100644 index 3ced9ca08a6a..000000000000 --- a/qiskit/qasm/node/format.py +++ /dev/null @@ -1,37 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM file identifier/version statement.""" - -import re - -from .node import Node - - -class Format(Node): - """Node for an OPENQASM file identifier/version statement.""" - - def __init__(self, value): - """Create the version node.""" - super().__init__("format", None, None) - parts = re.match(r"(\w+)\s+(\d+)(\.(\d+))?", value) - self.language = parts.group(1) - self.majorversion = parts.group(2) - self.minorversion = parts.group(4) if parts.group(4) is not None else "0" - - def version(self): - """Return the version.""" - return f"{self.majorversion}.{self.minorversion}" - - def qasm(self): - """Return the corresponding format string.""" - return f"{self.language} {self.version()};" diff --git a/qiskit/qasm/node/gate.py b/qiskit/qasm/node/gate.py deleted file mode 100644 index 122bd4f935df..000000000000 --- a/qiskit/qasm/node/gate.py +++ /dev/null @@ -1,62 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM gate definition.""" -from .node import Node - - -class Gate(Node): - """Node for an OPENQASM gate definition. - - children[0] is an id node. - If len(children) is 3, children[1] is an idlist node, - and children[2] is a gatebody node. - Otherwise, children[1] is an expressionlist node, - children[2] is an idlist node, and children[3] is a gatebody node. - """ - - def __init__(self, children): - """Create the gate node.""" - super().__init__("gate", children, None) - self.id = children[0] # pylint: disable=invalid-name - # The next three fields are required by the symbtab - self.name = self.id.name - self.line = self.id.line - self.file = self.id.file - - if len(children) == 3: - self.arguments = None - self.bitlist = children[1] - self.body = children[2] - else: - self.arguments = children[1] - self.bitlist = children[2] - self.body = children[3] - - def n_args(self): - """Return the number of parameter expressions.""" - if self.arguments: - return self.arguments.size() - return 0 - - def n_bits(self): - """Return the number of qubit arguments.""" - return self.bitlist.size() - - def qasm(self): - """Return the corresponding OPENQASM string.""" - string = "gate " + self.name - if self.arguments is not None: - string += "(" + self.arguments.qasm() + ")" - string += " " + self.bitlist.qasm() + "\n" - string += "{\n" + self.body.qasm() + "}" - return string diff --git a/qiskit/qasm/node/gatebody.py b/qiskit/qasm/node/gatebody.py deleted file mode 100644 index a7c591b549b8..000000000000 --- a/qiskit/qasm/node/gatebody.py +++ /dev/null @@ -1,41 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM custom gate body.""" -from .node import Node - - -class GateBody(Node): - """Node for an OPENQASM custom gate body. - - children is a list of gate operation nodes. - These are one of barrier, custom_unitary, U, or CX. - """ - - def __init__(self, children): - """Create the gatebody node.""" - super().__init__("gate_body", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - string = "" - for children in self.children: - string += " " + children.qasm() + "\n" - return string - - def calls(self): - """Return a list of custom gate names in this gate body.""" - lst = [] - for children in self.children: - if children.type == "custom_unitary": - lst.append(children.name) - return lst diff --git a/qiskit/qasm/node/id.py b/qiskit/qasm/node/id.py deleted file mode 100644 index 041ea452a4bf..000000000000 --- a/qiskit/qasm/node/id.py +++ /dev/null @@ -1,78 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM id.""" - -from .node import Node -from .nodeexception import NodeException - - -class Id(Node): - """Node for an OPENQASM id. - - The node has no children but has fields name, line, and file. - There is a flag is_bit that is set when XXXXX to help with scoping. - """ - - def __init__(self, id, line, file): - """Create the id node.""" - # pylint: disable=redefined-builtin - super().__init__("id", None, None) - self.name = id - self.line = line - self.file = file - # To help with scoping rules, so we know the id is a bit, - # this flag is set to True when the id appears in a gate declaration - self.is_bit = False - - def to_string(self, indent): - """Print the node with indent.""" - ind = indent * " " - print(ind, "id", self.name) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return self.name - - def latex(self, nested_scope=None): - """Return the correspond math mode latex string.""" - if not nested_scope: - return "\textrm{" + self.name + "}" - else: - if self.name not in nested_scope[-1]: - raise NodeException( - "Expected local parameter name: ", - "name=%s, " % self.name, - "line=%s, " % self.line, - "file=%s" % self.file, - ) - - return nested_scope[-1][self.name].latex(nested_scope[0:-1]) - - def sym(self, nested_scope=None): - """Return the correspond symbolic number.""" - if not nested_scope or self.name not in nested_scope[-1]: - raise NodeException( - "Expected local parameter name: ", - f"name={self.name}, line={self.line}, file={self.file}", - ) - return nested_scope[-1][self.name].sym(nested_scope[0:-1]) - - def real(self, nested_scope=None): - """Return the correspond floating point number.""" - if not nested_scope or self.name not in nested_scope[-1]: - raise NodeException( - "Expected local parameter name: ", - f"name={self.name}, line={self.line}, file={self.file}", - ) - - return nested_scope[-1][self.name].real(nested_scope[0:-1]) diff --git a/qiskit/qasm/node/idlist.py b/qiskit/qasm/node/idlist.py deleted file mode 100644 index 889fd887f8df..000000000000 --- a/qiskit/qasm/node/idlist.py +++ /dev/null @@ -1,33 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM idlist.""" -from .node import Node - - -class IdList(Node): - """Node for an OPENQASM idlist. - - children is a list of id nodes. - """ - - def __init__(self, children): - """Create the idlist node.""" - super().__init__("id_list", children, None) - - def size(self): - """Return the length of the list.""" - return len(self.children) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return ",".join([self.children[j].qasm() for j in range(self.size())]) diff --git a/qiskit/qasm/node/if_.py b/qiskit/qasm/node/if_.py deleted file mode 100644 index c056b078fe01..000000000000 --- a/qiskit/qasm/node/if_.py +++ /dev/null @@ -1,39 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM if statement.""" -from .node import Node - - -class If(Node): - """Node for an OPENQASM if statement. - - children[0] is an id node. - children[1] is an integer node. - children[2] is quantum operation node, including U, CX, custom_unitary, - measure, reset, (and BUG: barrier, if). - """ - - def __init__(self, children): - """Create the if node.""" - super().__init__("if", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return ( - "if(" - + self.children[0].qasm() - + "==" - + str(self.children[1].value) - + ") " - + self.children[2].qasm() - ) diff --git a/qiskit/qasm/node/indexedid.py b/qiskit/qasm/node/indexedid.py deleted file mode 100644 index ea6aa93444d6..000000000000 --- a/qiskit/qasm/node/indexedid.py +++ /dev/null @@ -1,41 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM indexed id.""" - -from .node import Node - - -class IndexedId(Node): - """Node for an OPENQASM indexed id. - - children[0] is an id node. - children[1] is an Int node. - """ - - def __init__(self, children): - """Create the indexed id node.""" - super().__init__("indexed_id", children, None) - self.id = children[0] # pylint: disable=invalid-name - self.name = self.id.name - self.line = self.id.line - self.file = self.id.file - self.index = children[1].value - - def to_string(self, indent): - """Print with indent.""" - ind = indent * " " - print(ind, "indexed_id", self.name, self.index) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return self.name + "[%d]" % self.index diff --git a/qiskit/qasm/node/intnode.py b/qiskit/qasm/node/intnode.py deleted file mode 100644 index 2b61e670c06b..000000000000 --- a/qiskit/qasm/node/intnode.py +++ /dev/null @@ -1,51 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM integer.""" - -from .node import Node - - -class Int(Node): - """Node for an OPENQASM integer. - - This node has no children. The data is in the value field. - """ - - def __init__(self, id): - """Create the integer node.""" - # pylint: disable=redefined-builtin - super().__init__("int", None, None) - self.value = id - - def to_string(self, indent): - """Print with indent.""" - ind = indent * " " - print(ind, "int", self.value) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "%d" % self.value - - def latex(self): - """Return the corresponding math mode latex string.""" - return "%d" % self.value - - def sym(self, nested_scope=None): - """Return the correspond symbolic number.""" - del nested_scope - return float(self.value) - - def real(self, nested_scope=None): - """Return the correspond floating point number.""" - del nested_scope # ignored - return float(self.value) diff --git a/qiskit/qasm/node/measure.py b/qiskit/qasm/node/measure.py deleted file mode 100644 index c2045fba54c8..000000000000 --- a/qiskit/qasm/node/measure.py +++ /dev/null @@ -1,30 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM measure statement.""" -from .node import Node - - -class Measure(Node): - """Node for an OPENQASM measure statement. - - children[0] is a primary node (id or indexedid) - children[1] is a primary node (id or indexedid) - """ - - def __init__(self, children): - """Create the measure node.""" - super().__init__("measure", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "measure " + self.children[0].qasm() + " -> " + self.children[1].qasm() + ";" diff --git a/qiskit/qasm/node/node.py b/qiskit/qasm/node/node.py deleted file mode 100644 index 6f64dfb1343f..000000000000 --- a/qiskit/qasm/node/node.py +++ /dev/null @@ -1,59 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Base node object for the OPENQASM syntax tree.""" - - -class Node: - """Base node object for the OPENQASM syntax tree.""" - - def __init__(self, type, children=None, root=None): - """Construct a new node object.""" - # pylint: disable=redefined-builtin - self.type = type - if children: - self.children = children - else: - self.children = [] - self.root = root - # True if this node is an expression node, False otherwise - self.expression = False - - def is_expression(self): - """Return True if this is an expression node.""" - return self.expression - - def add_child(self, node): - """Add a child node.""" - self.children.append(node) - - def to_string(self, indent): - """Print with indent.""" - ind = indent * " " - if self.root: - print(ind, self.type, "---", self.root) - else: - print(ind, self.type) - indent = indent + 3 - ind = indent * " " - for children in self.children: - if children is None: - print("OOPS! type of parent is", type(self)) - print(self.children) - if isinstance(children, str): - print(ind, children) - elif isinstance(children, int): - print(ind, str(children)) - elif isinstance(children, float): - print(ind, str(children)) - else: - children.to_string(indent) diff --git a/qiskit/qasm/node/nodeexception.py b/qiskit/qasm/node/nodeexception.py deleted file mode 100644 index d351f499c929..000000000000 --- a/qiskit/qasm/node/nodeexception.py +++ /dev/null @@ -1,26 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Exception for errors raised while interpreting nodes.""" - - -class NodeException(Exception): - """Base class for errors raised while interpreting nodes.""" - - def __init__(self, *msg): - """Set the error message.""" - super().__init__(*msg) - self.msg = " ".join(msg) - - def __str__(self): - """Return the message.""" - return repr(self.msg) diff --git a/qiskit/qasm/node/opaque.py b/qiskit/qasm/node/opaque.py deleted file mode 100644 index 866ee711840d..000000000000 --- a/qiskit/qasm/node/opaque.py +++ /dev/null @@ -1,58 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM opaque gate declaration.""" - -from .node import Node - - -class Opaque(Node): - """Node for an OPENQASM opaque gate declaration. - - children[0] is an id node. - If len(children) is 3, children[1] is an expressionlist node, - and children[2] is an idlist node. - Otherwise, children[1] is an idlist node. - """ - - def __init__(self, children): - """Create the opaque gate node.""" - super().__init__("opaque", children, None) - self.id = children[0] # pylint: disable=invalid-name - # The next three fields are required by the symbtab - self.name = self.id.name - self.line = self.id.line - self.file = self.id.file - if len(children) == 3: - self.arguments = children[1] - self.bitlist = children[2] - else: - self.arguments = None - self.bitlist = children[1] - - def n_args(self): - """Return the number of parameter expressions.""" - if self.arguments: - return self.arguments.size() - return 0 - - def n_bits(self): - """Return the number of qubit arguments.""" - return self.bitlist.size() - - def qasm(self): - """Return the corresponding OPENQASM string.""" - string = "opaque %s" % self.name - if self.arguments is not None: - string += "(" + self.arguments.qasm() + ")" - string += " " + self.bitlist.qasm() + ";" - return string diff --git a/qiskit/qasm/node/prefix.py b/qiskit/qasm/node/prefix.py deleted file mode 100644 index c144e2b5e280..000000000000 --- a/qiskit/qasm/node/prefix.py +++ /dev/null @@ -1,54 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM prefix expression.""" - -from qiskit.exceptions import MissingOptionalLibraryError -from .node import Node - - -class Prefix(Node): - """Node for an OPENQASM prefix expression. - - children[0] is a unary operator node. - children[1] is an expression node. - """ - - def __init__(self, children): - """Create the prefix node.""" - super().__init__("prefix", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return self.children[0].value + "(" + self.children[1].qasm() + ")" - - def latex(self): - """Return the corresponding math mode latex string.""" - try: - from pylatexenc.latexencode import utf8tolatex - except ImportError as ex: - raise MissingOptionalLibraryError( - "pylatexenc", "latex-from-qasm exporter", "pip install pylatexenc" - ) from ex - return utf8tolatex(self.sym()) - - def real(self): - """Return the correspond floating point number.""" - operation = self.children[0].operation() - expr = self.children[1].real() - return operation(expr) - - def sym(self, nested_scope=None): - """Return the correspond symbolic number.""" - operation = self.children[0].operation() - expr = self.children[1].sym(nested_scope) - return operation(expr) diff --git a/qiskit/qasm/node/primarylist.py b/qiskit/qasm/node/primarylist.py deleted file mode 100644 index 20e20f7b30d3..000000000000 --- a/qiskit/qasm/node/primarylist.py +++ /dev/null @@ -1,33 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM primarylist.""" -from .node import Node - - -class PrimaryList(Node): - """Node for an OPENQASM primarylist. - - children is a list of primary nodes. Primary nodes are indexedid or id. - """ - - def __init__(self, children): - """Create the primarylist node.""" - super().__init__("primary_list", children, None) - - def size(self): - """Return the size of the list.""" - return len(self.children) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return ",".join([self.children[j].qasm() for j in range(self.size())]) diff --git a/qiskit/qasm/node/program.py b/qiskit/qasm/node/program.py deleted file mode 100644 index 4475cb12f3d2..000000000000 --- a/qiskit/qasm/node/program.py +++ /dev/null @@ -1,32 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM program.""" -from .node import Node - - -class Program(Node): - """Node for an OPENQASM program. - - children is a list of nodes (statements). - """ - - def __init__(self, children): - """Create the program node.""" - super().__init__("program", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - string = "" - for children in self.children: - string += children.qasm() + "\n" - return string diff --git a/qiskit/qasm/node/qreg.py b/qiskit/qasm/node/qreg.py deleted file mode 100644 index fb384bd342e3..000000000000 --- a/qiskit/qasm/node/qreg.py +++ /dev/null @@ -1,45 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM qreg statement.""" -from .node import Node - - -class Qreg(Node): - """Node for an OPENQASM qreg statement. - - children[0] is an indexedid node. - """ - - def __init__(self, children): - """Create the qreg node.""" - super().__init__("qreg", children, None) - # This is the indexed id, the full "id[n]" object - self.id = children[0] # pylint: disable=invalid-name - # Name of the qreg - self.name = self.id.name - # Source line number - self.line = self.id.line - # Source file name - self.file = self.id.file - # Size of the register - self.index = self.id.index - - def to_string(self, indent): - """Print the node data, with indent.""" - ind = indent * " " - print(ind, "qreg") - self.children[0].to_string(indent + 3) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "qreg " + self.id.qasm() + ";" diff --git a/qiskit/qasm/node/real.py b/qiskit/qasm/node/real.py deleted file mode 100644 index f967b7582502..000000000000 --- a/qiskit/qasm/node/real.py +++ /dev/null @@ -1,63 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM real number.""" - -import numpy as np - -from qiskit.exceptions import MissingOptionalLibraryError -from .node import Node - - -class Real(Node): - """Node for an OPENQASM real number. - - This node has no children. The data is in the value field. - """ - - def __init__(self, id): - """Create the real node.""" - # pylint: disable=redefined-builtin - super().__init__("real", None, None) - self.value = id - - def to_string(self, indent): - """Print with indent.""" - ind = indent * " " - print(ind, "real", self.value) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - if self.value == np.pi: - return "pi" - - return str(np.round(float(self.value))) - - def latex(self): - """Return the corresponding math mode latex string.""" - try: - from pylatexenc.latexencode import utf8tolatex - except ImportError as ex: - raise MissingOptionalLibraryError( - "pylatexenc", "latex-from-qasm exporter", "pip install pylatexenc" - ) from ex - return utf8tolatex(self.value) - - def sym(self, nested_scope=None): - """Return the correspond symbolic number.""" - del nested_scope # unused - return float(self.value) - - def real(self, nested_scope=None): - """Return the correspond floating point number.""" - del nested_scope # unused - return float(self.value.evalf()) diff --git a/qiskit/qasm/node/reset.py b/qiskit/qasm/node/reset.py deleted file mode 100644 index 29ccae931d96..000000000000 --- a/qiskit/qasm/node/reset.py +++ /dev/null @@ -1,29 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM reset statement.""" -from .node import Node - - -class Reset(Node): - """Node for an OPENQASM reset statement. - - children[0] is a primary node (id or indexedid) - """ - - def __init__(self, children): - """Create the reset node.""" - super().__init__("reset", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "reset " + self.children[0].qasm() + ";" diff --git a/qiskit/qasm/node/unaryoperator.py b/qiskit/qasm/node/unaryoperator.py deleted file mode 100644 index a81e9f0737b1..000000000000 --- a/qiskit/qasm/node/unaryoperator.py +++ /dev/null @@ -1,49 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OpenQASM 2 unary operator.""" - -import operator - -from .node import Node -from .nodeexception import NodeException - - -VALID_OPERATORS = { - "+": operator.pos, - "-": operator.neg, -} - - -class UnaryOperator(Node): - """Node for an OpenQASM 2 unary operator. - - This node has no children. The data is in the value field. - """ - - def __init__(self, operation): - """Create the operator node.""" - super().__init__("unary_operator", None, None) - self.value = operation - - def operation(self): - """ - Return the operator as a function f(left, right). - """ - try: - return VALID_OPERATORS[self.value] - except KeyError as ex: - raise NodeException(f"internal error: undefined prefix '{self.value}'") from ex - - def qasm(self): - """Return OpenQASM 2 representation.""" - return self.value diff --git a/qiskit/qasm/node/universalunitary.py b/qiskit/qasm/node/universalunitary.py deleted file mode 100644 index e00303821273..000000000000 --- a/qiskit/qasm/node/universalunitary.py +++ /dev/null @@ -1,32 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM U statement.""" -from .node import Node - - -class UniversalUnitary(Node): - """Node for an OPENQASM U statement. - - children[0] is an expressionlist node. - children[1] is a primary node (id or indexedid). - """ - - def __init__(self, children): - """Create the U node.""" - super().__init__("universal_unitary", children) - self.arguments = children[0] - self.bitlist = children[1] - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "U(" + self.children[0].qasm() + ") " + self.children[1].qasm() + ";" diff --git a/qiskit/qasm/pygments/__init__.py b/qiskit/qasm/pygments/__init__.py deleted file mode 100644 index 686bf5e7800d..000000000000 --- a/qiskit/qasm/pygments/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -================================================= -Qasm Pygments tools (:mod:`qiskit.qasm.pygments`) -================================================= - -.. currentmodule:: qiskit.qasm.pygments - -.. autosummary:: - :toctree: ../stubs/ - - OpenQASMLexer - QasmTerminalStyle - QasmHTMLStyle -""" - -# pylint: disable=wrong-import-position - -from qiskit.utils.optionals import HAS_PYGMENTS - -HAS_PYGMENTS.require_now("built-in OpenQASM 2 syntax highlighting") - -from .lexer import OpenQASMLexer, QasmTerminalStyle, QasmHTMLStyle diff --git a/qiskit/qasm/pygments/lexer.py b/qiskit/qasm/pygments/lexer.py deleted file mode 100644 index cba2163bb0d7..000000000000 --- a/qiskit/qasm/pygments/lexer.py +++ /dev/null @@ -1,133 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Pygments tools for Qasm. -""" - -from pygments.lexer import RegexLexer -from pygments.token import Comment, String, Keyword, Name, Number, Text -from pygments.style import Style - - -class QasmTerminalStyle(Style): - """A style for OpenQasm in a Terminal env (e.g. Jupyter print).""" - - styles = { - String: "ansibrightred", - Number: "ansibrightcyan", - Keyword.Reserved: "ansibrightgreen", - Keyword.Declaration: "ansibrightgreen", - Keyword.Type: "ansibrightmagenta", - Name.Builtin: "ansibrightblue", - Name.Function: "ansibrightyellow", - } - - -class QasmHTMLStyle(Style): - """A style for OpenQasm in a HTML env (e.g. Jupyter widget).""" - - styles = { - String: "ansired", - Number: "ansicyan", - Keyword.Reserved: "ansigreen", - Keyword.Declaration: "ansigreen", - Keyword.Type: "ansimagenta", - Name.Builtin: "ansiblue", - Name.Function: "ansiyellow", - } - - -class OpenQASMLexer(RegexLexer): - """A pygments lexer for OpenQasm.""" - - name = "OpenQASM" - aliases = ["qasm"] - filenames = ["*.qasm"] - - gates = [ - "id", - "cx", - "x", - "y", - "z", - "s", - "sdg", - "h", - "t", - "tdg", - "ccx", - "c3x", - "c4x", - "c3sqrtx", - "rx", - "ry", - "rz", - "cz", - "cy", - "ch", - "swap", - "cswap", - "crx", - "cry", - "crz", - "cu1", - "cu3", - "rxx", - "rzz", - "rccx", - "rc3x", - "u1", - "u2", - "u3", - ] - - tokens = { - "root": [ - (r"\n", Text), - (r"[^\S\n]+", Text), - (r"//\n", Comment), - (r"//.*?$", Comment.Single), - # Keywords - (r"(OPENQASM|include)\b", Keyword.Reserved, "keywords"), - (r"(qreg|creg)\b", Keyword.Declaration), - # Treat 'if' special - (r"(if)\b", Keyword.Reserved, "if_keywords"), - # Constants - (r"(pi)\b", Name.Constant), - # Special - (r"(barrier|measure|reset)\b", Name.Builtin, "params"), - # Gates (Types) - ("(" + "|".join(gates) + r")\b", Keyword.Type, "params"), - (r"[unitary\d+]", Keyword.Type), - # Functions - (r"(gate)\b", Name.Function, "gate"), - # Generic text - (r"[a-zA-Z_][a-zA-Z0-9_]*", Text, "index"), - ], - "keywords": [ - (r'\s*("([^"]|"")*")', String, "#push"), - (r"\d+", Number, "#push"), - (r".*\(", Text, "params"), - ], - "if_keywords": [ - (r"[a-zA-Z0-9_]*", String, "#pop"), - (r"\d+", Number, "#push"), - (r".*\(", Text, "params"), - ], - "params": [ - (r"[a-zA-Z_][a-zA-Z0-9_]*", Text, "#push"), - (r"\d+", Number, "#push"), - (r"(\d+\.\d*|\d*\.\d+)([eEf][+-]?[0-9]+)?", Number, "#push"), - (r"\)", Text), - ], - "gate": [(r"[unitary\d+]", Keyword.Type, "#push"), (r"p\d+", Text, "#push")], - "index": [(r"\d+", Number, "#pop")], - } diff --git a/qiskit/qasm/qasm.py b/qiskit/qasm/qasm.py deleted file mode 100644 index ded52b32d3fc..000000000000 --- a/qiskit/qasm/qasm.py +++ /dev/null @@ -1,53 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -OPENQASM circuit object. -""" -from .exceptions import QasmError -from .qasmparser import QasmParser - - -class Qasm: - """OPENQASM circuit object.""" - - def __init__(self, filename=None, data=None): - """Create an OPENQASM circuit object.""" - if filename is None and data is None: - raise QasmError("Missing input file and/or data") - if filename is not None and data is not None: - raise QasmError("File and data must not both be specified initializing OpenQASM 2") - self._filename = filename - self._data = data - - def return_filename(self): - """Return the filename.""" - return self._filename - - def generate_tokens(self): - """Returns a generator of the tokens.""" - if self._filename: - with open(self._filename) as ifile: - self._data = ifile.read() - - with QasmParser(self._filename) as qasm_p: - return qasm_p.read_tokens() - - def parse(self): - """Parse the data.""" - if self._filename: - with open(self._filename) as ifile: - self._data = ifile.read() - - with QasmParser(self._filename) as qasm_p: - qasm_p.parse_debug(False) - return qasm_p.parse(self._data) diff --git a/qiskit/qasm/qasmlexer.py b/qiskit/qasm/qasmlexer.py deleted file mode 100644 index 7766d81c2eec..000000000000 --- a/qiskit/qasm/qasmlexer.py +++ /dev/null @@ -1,203 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -OPENQASM Lexer. - -This is a wrapper around the PLY lexer to support the "include" statement -by creating a stack of lexers. -""" - -import os - -import numpy as np -from ply import lex - -from . import node -from .exceptions import QasmError - -CORE_LIBS_PATH = os.path.join(os.path.dirname(__file__), "libs") -CORE_LIBS = os.listdir(CORE_LIBS_PATH) - - -class QasmLexer: - """OPENQASM Lexer. - - This is a wrapper around the PLY lexer to support the "include" statement - by creating a stack of lexers. - """ - - # pylint: disable=invalid-name,missing-function-docstring - # pylint: disable=attribute-defined-outside-init,bad-docstring-quotes - - def __mklexer__(self, filename): - """Create a PLY lexer.""" - self.lexer = lex.lex(module=self, debug=False) - self.filename = filename - self.lineno = 1 - - if filename: - with open(filename) as ifile: - self.data = ifile.read() - self.lexer.input(self.data) - - def __init__(self, filename): - """Create the OPENQASM lexer.""" - self.__mklexer__(filename) - self.stack = [] - - def input(self, data): - """Set the input text data.""" - self.data = data - self.lexer.input(data) - - def token(self): - """Return the next token.""" - ret = self.lexer.token() - return ret - - def pop(self): - """Pop a PLY lexer off the stack.""" - self.lexer = self.stack.pop() - self.filename = self.lexer.qasm_file - self.lineno = self.lexer.qasm_line - - def push(self, filename): - """Push a PLY lexer on the stack to parse filename.""" - self.lexer.qasm_file = self.filename - self.lexer.qasm_line = self.lineno - self.stack.append(self.lexer) - self.__mklexer__(filename) - - # ---- Beginning of the PLY lexer ---- - literals = r'=()[]{};<>,.+-/*^"' - reserved = { - "barrier": "BARRIER", - "creg": "CREG", - "gate": "GATE", - "if": "IF", - "measure": "MEASURE", - "opaque": "OPAQUE", - "qreg": "QREG", - "pi": "PI", - "reset": "RESET", - } - tokens = [ - "NNINTEGER", - "REAL", - "CX", - "U", - "FORMAT", - "ASSIGN", - "MATCHES", - "ID", - "STRING", - ] + list(reserved.values()) - - def t_REAL(self, t): - r"(([0-9]+|([0-9]+)?\.[0-9]+|[0-9]+\.)[eE][+-]?[0-9]+)|(([0-9]+)?\.[0-9]+|[0-9]+\.)" - if np.iscomplex(t): - return t.real - else: - return t - - def t_NNINTEGER(self, t): - r"[1-9]+[0-9]*|0" - t.value = int(t.value) - return t - - def t_ASSIGN(self, t): - "->" - return t - - def t_MATCHES(self, t): - "==" - return t - - def t_STRING(self, t): - r"\"([^\\\"]|\\.)*\"" # fmt: skip - return t - - def t_INCLUDE(self, _): - "include" - # Now eat up the next two tokens which must be - # 1 - the name of the include file, and - # 2 - a terminating semicolon - # - # Then push the current lexer onto the stack, create a new one from - # the include file, and push it onto the stack. - # - # When we hit eof (the t_eof) rule, we pop. - next_token = self.lexer.token() - lineno = next_token.lineno - if isinstance(next_token.value, str): - incfile = next_token.value.strip('"') - else: - raise QasmError("Invalid include: must be a quoted string.") - - if incfile in CORE_LIBS: - incfile = os.path.join(CORE_LIBS_PATH, incfile) - - next_token = self.lexer.token() - if next_token is None or next_token.value != ";": - raise QasmError('Invalid syntax, missing ";" at line', str(lineno)) - - if not os.path.exists(incfile): - raise QasmError( - "Include file %s cannot be found, line %s, file %s" - % (incfile, str(next_token.lineno), self.filename) - ) - self.push(incfile) - return self.lexer.token() - - def t_FORMAT(self, t): - r"OPENQASM\s+[0-9]+(\.[0-9]+)?" - return t - - def t_COMMENT(self, _): - r"//.*" - pass - - def t_CX(self, t): - "CX" - return t - - def t_U(self, t): - "U" - return t - - def t_ID(self, t): - r"[a-z][a-zA-Z0-9_]*" - - t.type = self.reserved.get(t.value, "ID") - if t.type == "ID": - t.value = node.Id(t.value, self.lineno, self.filename) - return t - - def t_newline(self, t): - r"\n+" - self.lineno += len(t.value) - t.lexer.lineno = self.lineno - - def t_eof(self, _): - if self.stack: - self.pop() - return self.lexer.token() - return None - - t_ignore = " \t\r" - - def t_error(self, t): - raise QasmError( - "Unable to match any token rule, got -->%s<-- " - "Check your OPENQASM source and any include statements." % t.value[0] - ) diff --git a/qiskit/qasm/qasmparser.py b/qiskit/qasm/qasmparser.py deleted file mode 100644 index f5c2dc2c1fa0..000000000000 --- a/qiskit/qasm/qasmparser.py +++ /dev/null @@ -1,1156 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""OpenQASM parser.""" - -import os -import shutil -import tempfile - -import numpy as np -from ply import yacc - -from . import node -from .exceptions import QasmError -from .qasmlexer import QasmLexer - - -class QasmParser: - """OPENQASM Parser.""" - - # pylint: disable=missing-function-docstring,invalid-name - - def __init__(self, filename): - """Create the parser.""" - if filename is None: - filename = "" - self.lexer = QasmLexer(filename) - self.tokens = self.lexer.tokens - self.parse_dir = tempfile.mkdtemp(prefix="qiskit") - self.precedence = ( - ("left", "+", "-"), - ("left", "*", "/"), - ("left", "negative", "positive"), - ("right", "^"), - ) - # For yacc, also, write_tables = Bool and optimize = Bool - self.parser = yacc.yacc(module=self, debug=False, outputdir=self.parse_dir) - self.qasm = None - self.parse_deb = False - self.global_symtab = {} # global symtab - self.current_symtab = self.global_symtab # top of symbol stack - self.symbols = [] # symbol stack - self.external_functions = ["sin", "cos", "tan", "exp", "ln", "sqrt", "acos", "atan", "asin"] - - def __enter__(self): - return self - - def __exit__(self, *args): - if os.path.exists(self.parse_dir): - shutil.rmtree(self.parse_dir) - - def update_symtab(self, obj): - """Update a node in the symbol table. - - Everything in the symtab must be a node with these attributes: - name - the string name of the object - type - the string type of the object - line - the source line where the type was first found - file - the source file where the type was first found - """ - if obj.name in self.current_symtab: - prev = self.current_symtab[obj.name] - raise QasmError( - "Duplicate declaration for", - obj.type + " '" + obj.name + "' at line", - str(obj.line) + ", file", - obj.file + ".\nPrevious occurrence at line", - str(prev.line) + ", file", - prev.file, - ) - self.current_symtab[obj.name] = obj - - def verify_declared_bit(self, obj): - """Verify a qubit id against the gate prototype.""" - # We are verifying gate args against the formal parameters of a - # gate prototype. - if obj.name not in self.current_symtab: - raise QasmError( - "Cannot find symbol '" + obj.name + "' in argument list for gate, line", - str(obj.line), - "file", - obj.file, - ) - - # This insures the thing is from the bitlist and not from the - # argument list. - sym = self.current_symtab[obj.name] - if not (sym.type == "id" and sym.is_bit): - raise QasmError("Bit", obj.name, "is not declared as a bit in the gate.") - - def verify_bit_list(self, obj): - """Verify each qubit in a list of ids.""" - # We expect the object to be a bitlist or an idlist, we don't care. - # We will iterate it and ensure everything in it is declared as a bit, - # and throw if not. - for children in obj.children: - self.verify_declared_bit(children) - - def verify_exp_list(self, obj): - """Verify each expression in a list.""" - # A tad harder. This is a list of expressions each of which could be - # the head of a tree. We need to recursively walk each of these and - # ensure that any Id elements resolve to the current stack. - # - # I believe we only have to look at the current symtab. - if obj.children is not None: - for children in obj.children: - if isinstance(children, node.Id): - if children.name in self.external_functions: - continue - - if children.name not in self.current_symtab: - raise QasmError( - "Argument '" - + children.name - + "' in expression cannot be " - + "found, line", - str(children.line), - "file", - children.file, - ) - else: - if hasattr(children, "children"): - self.verify_exp_list(children) - - def verify_as_gate(self, obj, bitlist, arglist=None): - """Verify a user defined gate call.""" - if obj.name not in self.global_symtab: - raise QasmError( - "Cannot find gate definition for '" + obj.name + "', line", - str(obj.line), - "file", - obj.file, - ) - g_sym = self.global_symtab[obj.name] - if g_sym.type not in ("gate", "opaque"): - raise QasmError( - "'" - + obj.name - + "' is used as a gate " - + "or opaque call but the symbol is neither;" - + " it is a '" - + g_sym.type - + "' line", - str(obj.line), - "file", - obj.file, - ) - - if g_sym.n_bits() != bitlist.size(): - raise QasmError( - "Gate or opaque call to '" + obj.name + "' uses", - str(bitlist.size()), - "qubits but is declared for", - str(g_sym.n_bits()), - "qubits", - "line", - str(obj.line), - "file", - obj.file, - ) - - if arglist: - if g_sym.n_args() != arglist.size(): - raise QasmError( - "Gate or opaque call to '" + obj.name + "' uses", - str(arglist.size()), - "qubits but is declared for", - str(g_sym.n_args()), - "qubits", - "line", - str(obj.line), - "file", - obj.file, - ) - else: - if g_sym.n_args() > 0: - raise QasmError( - "Gate or opaque call to '" - + obj.name - + "' has no arguments but is declared for", - str(g_sym.n_args()), - "qubits", - "line", - str(obj.line), - "file", - obj.file, - ) - - def verify_reg(self, obj, object_type): - """Verify a register.""" - # How to verify: - # types must match - # indexes must be checked - if obj.name not in self.global_symtab: - raise QasmError( - "Cannot find definition for", - object_type, - "'" + obj.name + "'", - "at line", - str(obj.line), - "file", - obj.file, - ) - - g_sym = self.global_symtab[obj.name] - - if g_sym.type != object_type: - raise QasmError( - "Type for '" - + g_sym.name - + "' should be '" - + object_type - + "' but was found to be '" - + g_sym.type - + "'", - "line", - str(obj.line), - "file", - obj.file, - ) - - if obj.type == "indexed_id": - bound = g_sym.index - ndx = obj.index - if ndx < 0 or ndx >= bound: - raise QasmError( - "Register index for '" + g_sym.name + "' out of bounds. Index is", - str(ndx), - "bound is 0 <= index <", - str(bound), - "at line", - str(obj.line), - "file", - obj.file, - ) - - def verify_reg_list(self, obj, object_type): - """Verify a list of registers.""" - # We expect the object to be a bitlist or an idlist, we don't care. - # We will iterate it and ensure everything in it is declared as a bit, - # and throw if not. - for children in obj.children: - self.verify_reg(children, object_type) - - def id_tuple_list(self, id_node): - """Return a list of (name, index) tuples for this id node.""" - if id_node.type != "id": - raise QasmError("internal error, id_tuple_list") - bit_list = [] - try: - g_sym = self.current_symtab[id_node.name] - except KeyError: - g_sym = self.global_symtab[id_node.name] - if g_sym.type in ("qreg", "creg"): - # Return list of (name, idx) for reg ids - for idx in range(g_sym.index): - bit_list.append((id_node.name, idx)) - else: - # Return (name, -1) for other ids - bit_list.append((id_node.name, -1)) - return bit_list - - def verify_distinct(self, list_of_nodes): - """Check that objects in list_of_nodes represent distinct (qu)bits. - - list_of_nodes is a list containing nodes of type id, indexed_id, - primary_list, or id_list. We assume these are all the same type - 'qreg' or 'creg'. - This method raises an exception if list_of_nodes refers to the - same object more than once. - """ - bit_list = [] - line_number = -1 - filename = "" - for node_ in list_of_nodes: - # id node: add all bits in register or (name, -1) for id - if node_.type == "id": - bit_list.extend(self.id_tuple_list(node_)) - line_number = node_.line - filename = node_.file - # indexed_id: add the bit - elif node_.type == "indexed_id": - bit_list.append((node_.name, node_.index)) - line_number = node_.line - filename = node_.file - # primary_list: for each id or indexed_id child, add - elif node_.type == "primary_list": - for child in node_.children: - if child.type == "id": - bit_list.extend(self.id_tuple_list(child)) - else: - bit_list.append((child.name, child.index)) - line_number = child.line - filename = child.file - # id_list: for each id, add - elif node_.type == "id_list": - for child in node_.children: - bit_list.extend(self.id_tuple_list(child)) - line_number = child.line - filename = child.file - else: - raise QasmError("internal error, verify_distinct") - if len(bit_list) != len(set(bit_list)): - raise QasmError("duplicate identifiers at line %d file %s" % (line_number, filename)) - - def pop_scope(self): - """Return to the previous scope.""" - self.current_symtab = self.symbols.pop() - - def push_scope(self): - """Enter a new scope.""" - self.symbols.append(self.current_symtab) - self.current_symtab = {} - - # ---- Begin the PLY parser ---- - start = "main" - - def p_main(self, program): - """ - main : program - """ - self.qasm = program[1] - - # ---------------------------------------- - # program : statement - # | program statement - # ---------------------------------------- - def p_program_0(self, program): - """ - program : statement - """ - program[0] = node.Program([program[1]]) - - def p_program_1(self, program): - """ - program : program statement - """ - program[0] = program[1] - program[0].add_child(program[2]) - - # ---------------------------------------- - # statement : decl - # | quantum_op ';' - # | format ';' - # ---------------------------------------- - def p_statement(self, program): - """ - statement : decl - | quantum_op ';' - | format ';' - | ignore - | quantum_op error - | format error - """ - if len(program) > 2: - if program[2] != ";": - raise QasmError( - "Missing ';' at end of statement; " + "received", str(program[2].value) - ) - program[0] = program[1] - - def p_format(self, program): - """ - format : FORMAT - """ - version = node.Format(program[1]) - if (version.majorversion != "2") or (version.minorversion != "0"): - provided_version = f"{version.majorversion}.{version.minorversion}" - raise QasmError( - f"Invalid version: '{provided_version}'. This module supports OpenQASM 2.0 only." - ) - program[0] = version - - # ---------------------------------------- - # id : ID - # ---------------------------------------- - def p_id(self, program): - """ - id : ID - """ - program[0] = program[1] - - def p_id_e(self, program): - """ - id : error - """ - raise QasmError("Expected an ID, received '" + str(program[1].value) + "'") - - # ---------------------------------------- - # indexed_id : ID [ int ] - # ---------------------------------------- - def p_indexed_id(self, program): - """ - indexed_id : id '[' NNINTEGER ']' - | id '[' NNINTEGER error - | id '[' error - """ - if len(program) == 4: - raise QasmError("Expecting an integer index; received", str(program[3].value)) - if program[4] != "]": - raise QasmError("Missing ']' in indexed ID; received", str(program[4].value)) - program[0] = node.IndexedId([program[1], node.Int(program[3])]) - - # ---------------------------------------- - # primary : id - # | indexed_id - # ---------------------------------------- - def p_primary(self, program): - """ - primary : id - | indexed_id - """ - program[0] = program[1] - - # ---------------------------------------- - # id_list : id - # | id_list ',' id - # ---------------------------------------- - def p_id_list_0(self, program): - """ - id_list : id - """ - program[0] = node.IdList([program[1]]) - - def p_id_list_1(self, program): - """ - id_list : id_list ',' id - """ - program[0] = program[1] - program[0].add_child(program[3]) - - # ---------------------------------------- - # gate_id_list : id - # | gate_id_list ',' id - # ---------------------------------------- - def p_gate_id_list_0(self, program): - """ - gate_id_list : id - """ - program[0] = node.IdList([program[1]]) - self.update_symtab(program[1]) - - def p_gate_id_list_1(self, program): - """ - gate_id_list : gate_id_list ',' id - """ - program[0] = program[1] - program[0].add_child(program[3]) - self.update_symtab(program[3]) - - # ---------------------------------------- - # bit_list : bit - # | bit_list ',' bit - # ---------------------------------------- - def p_bit_list_0(self, program): - """ - bit_list : id - """ - program[0] = node.IdList([program[1]]) - program[1].is_bit = True - self.update_symtab(program[1]) - - def p_bit_list_1(self, program): - """ - bit_list : bit_list ',' id - """ - program[0] = program[1] - program[0].add_child(program[3]) - program[3].is_bit = True - self.update_symtab(program[3]) - - # ---------------------------------------- - # primary_list : primary - # | primary_list ',' primary - # ---------------------------------------- - def p_primary_list_0(self, program): - """ - primary_list : primary - """ - program[0] = node.PrimaryList([program[1]]) - - def p_primary_list_1(self, program): - """ - primary_list : primary_list ',' primary - """ - program[0] = program[1] - program[1].add_child(program[3]) - - # ---------------------------------------- - # decl : qreg_decl - # | creg_decl - # | gate_decl - # ---------------------------------------- - def p_decl(self, program): - """ - decl : qreg_decl ';' - | creg_decl ';' - | qreg_decl error - | creg_decl error - | gate_decl - """ - if len(program) > 2: - if program[2] != ";": - raise QasmError( - "Missing ';' in qreg or creg declaration." - " Instead received '" + program[2].value + "'" - ) - program[0] = program[1] - - # ---------------------------------------- - # qreg_decl : QREG indexed_id - # ---------------------------------------- - def p_qreg_decl(self, program): - """ - qreg_decl : QREG indexed_id - """ - program[0] = node.Qreg([program[2]]) - if program[2].name in self.external_functions: - raise QasmError( - "QREG names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - if program[2].index == 0: - raise QasmError("QREG size must be positive") - self.update_symtab(program[0]) - - def p_qreg_decl_e(self, program): - """ - qreg_decl : QREG error - """ - raise QasmError( - "Expecting indexed id (ID[int]) in QREG" + " declaration; received", program[2].value - ) - - # ---------------------------------------- - # creg_decl : QREG indexed_id - # ---------------------------------------- - def p_creg_decl(self, program): - """ - creg_decl : CREG indexed_id - """ - program[0] = node.Creg([program[2]]) - if program[2].name in self.external_functions: - raise QasmError( - "CREG names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - if program[2].index == 0: - raise QasmError("CREG size must be positive") - self.update_symtab(program[0]) - - def p_creg_decl_e(self, program): - """ - creg_decl : CREG error - """ - raise QasmError( - "Expecting indexed id (ID[int]) in CREG" + " declaration; received", program[2].value - ) - - # Gate_body will throw if there are errors, so we don't need to cover - # that here. Same with the id_lists - if they are not legal, we die - # before we get here - # - # ---------------------------------------- - # gate_decl : GATE id gate_scope bit_list gate_body - # | GATE id gate_scope '(' ')' bit_list gate_body - # | GATE id gate_scope '(' gate_id_list ')' bit_list gate_body - # - # ---------------------------------------- - def p_gate_decl_0(self, program): - """ - gate_decl : GATE id gate_scope bit_list gate_body - """ - program[0] = node.Gate([program[2], program[4], program[5]]) - if program[2].name in self.external_functions: - raise QasmError( - "GATE names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - self.pop_scope() - self.update_symtab(program[0]) - - def p_gate_decl_1(self, program): - """ - gate_decl : GATE id gate_scope '(' ')' bit_list gate_body - """ - program[0] = node.Gate([program[2], program[6], program[7]]) - if program[2].name in self.external_functions: - raise QasmError( - "GATE names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - self.pop_scope() - self.update_symtab(program[0]) - - def p_gate_decl_2(self, program): - """ - gate_decl : GATE id gate_scope '(' gate_id_list ')' bit_list gate_body - """ - program[0] = node.Gate([program[2], program[5], program[7], program[8]]) - if program[2].name in self.external_functions: - raise QasmError( - "GATE names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - self.pop_scope() - self.update_symtab(program[0]) - - def p_gate_scope(self, _): - """ - gate_scope : - """ - self.push_scope() - - # ---------------------------------------- - # gate_body : '{' gate_op_list '}' - # | '{' '}' - # - # | '{' gate_op_list error - # | '{' error - # - # Error handling: gete_op will throw if there's a problem so we won't - # get here with in the gate_op_list - # ---------------------------------------- - def p_gate_body_0(self, program): - """ - gate_body : '{' '}' - """ - if program[2] != "}": - raise QasmError( - "Missing '}' in gate definition; received'" + str(program[2].value) + "'" - ) - program[0] = node.GateBody(None) - - def p_gate_body_1(self, program): - """ - gate_body : '{' gate_op_list '}' - """ - program[0] = node.GateBody(program[2]) - - # ---------------------------------------- - # gate_op_list : gate_op - # | gate_op_ist gate_op - # - # Error handling: gete_op will throw if there's a problem so we won't - # get here with errors - # ---------------------------------------- - def p_gate_op_list_0(self, program): - """ - gate_op_list : gate_op - """ - program[0] = [program[1]] - - def p_gate_op_list_1(self, program): - """ - gate_op_list : gate_op_list gate_op - """ - program[0] = program[1] - program[0].append(program[2]) - - # ---------------------------------------- - # These are for use outside of gate_bodies and allow - # indexed ids everywhere. - # - # unitary_op : U '(' exp_list ')' primary - # | CX primary ',' primary - # | id primary_list - # | id '(' ')' primary_list - # | id '(' exp_list ')' primary_list - # - # Note that it might not be unitary - this is the mechanism that - # is also used to invoke calls to 'opaque' - # ---------------------------------------- - def p_unitary_op_0(self, program): - """ - unitary_op : U '(' exp_list ')' primary - """ - program[0] = node.UniversalUnitary([program[3], program[5]]) - self.verify_reg(program[5], "qreg") - self.verify_exp_list(program[3]) - - def p_unitary_op_1(self, program): - """ - unitary_op : CX primary ',' primary - """ - program[0] = node.Cnot([program[2], program[4]]) - self.verify_reg(program[2], "qreg") - self.verify_reg(program[4], "qreg") - self.verify_distinct([program[2], program[4]]) - # TODO: check that if both primary are id, same size - # TODO: this needs to be checked in other cases too - - def p_unitary_op_2(self, program): - """ - unitary_op : id primary_list - """ - program[0] = node.CustomUnitary([program[1], program[2]]) - self.verify_as_gate(program[1], program[2]) - self.verify_reg_list(program[2], "qreg") - self.verify_distinct([program[2]]) - - def p_unitary_op_3(self, program): - """ - unitary_op : id '(' ')' primary_list - """ - program[0] = node.CustomUnitary([program[1], program[4]]) - self.verify_as_gate(program[1], program[4]) - self.verify_reg_list(program[4], "qreg") - self.verify_distinct([program[4]]) - - def p_unitary_op_4(self, program): - """ - unitary_op : id '(' exp_list ')' primary_list - """ - program[0] = node.CustomUnitary([program[1], program[3], program[5]]) - self.verify_as_gate(program[1], program[5], arglist=program[3]) - self.verify_reg_list(program[5], "qreg") - self.verify_exp_list(program[3]) - self.verify_distinct([program[5]]) - - # ---------------------------------------- - # This is a restricted set of "quantum_op" which also - # prohibits indexed ids, for use in a gate_body - # - # gate_op : U '(' exp_list ')' id ';' - # | CX id ',' id ';' - # | id id_list ';' - # | id '(' ')' id_list ';' - # | id '(' exp_list ')' id_list ';' - # | BARRIER id_list ';' - # ---------------------------------------- - def p_gate_op_0(self, program): - """ - gate_op : U '(' exp_list ')' id ';' - """ - program[0] = node.UniversalUnitary([program[3], program[5]]) - self.verify_declared_bit(program[5]) - self.verify_exp_list(program[3]) - - def p_gate_op_0e1(self, p): - """ - gate_op : U '(' exp_list ')' error - """ - raise QasmError("Invalid U inside gate definition. " + "Missing bit id or ';'") - - def p_gate_op_0e2(self, _): - """ - gate_op : U '(' exp_list error - """ - raise QasmError("Missing ')' in U invocation in gate definition.") - - def p_gate_op_1(self, program): - """ - gate_op : CX id ',' id ';' - """ - program[0] = node.Cnot([program[2], program[4]]) - self.verify_declared_bit(program[2]) - self.verify_declared_bit(program[4]) - self.verify_distinct([program[2], program[4]]) - - def p_gate_op_1e1(self, program): - """ - gate_op : CX error - """ - raise QasmError( - "Invalid CX inside gate definition. " - + "Expected an ID or ',', received '" - + str(program[2].value) - + "'" - ) - - def p_gate_op_1e2(self, program): - """ - gate_op : CX id ',' error - """ - raise QasmError( - "Invalid CX inside gate definition. " - + "Expected an ID or ';', received '" - + str(program[4].value) - + "'" - ) - - def p_gate_op_2(self, program): - """ - gate_op : id id_list ';' - """ - program[0] = node.CustomUnitary([program[1], program[2]]) - # To verify: - # 1. id is declared as a gate in global scope - # 2. everything in the id_list is declared as a bit in local scope - self.verify_as_gate(program[1], program[2]) - self.verify_bit_list(program[2]) - self.verify_distinct([program[2]]) - - def p_gate_op_2e(self, _): - """ - gate_op : id id_list error - """ - raise QasmError("Invalid gate invocation inside gate definition.") - - def p_gate_op_3(self, program): - """ - gate_op : id '(' ')' id_list ';' - """ - program[0] = node.CustomUnitary([program[1], program[4]]) - self.verify_as_gate(program[1], program[4]) - self.verify_bit_list(program[4]) - self.verify_distinct([program[4]]) - - def p_gate_op_4(self, program): - """ - gate_op : id '(' exp_list ')' id_list ';' - """ - program[0] = node.CustomUnitary([program[1], program[3], program[5]]) - self.verify_as_gate(program[1], program[5], arglist=program[3]) - self.verify_bit_list(program[5]) - self.verify_exp_list(program[3]) - self.verify_distinct([program[5]]) - - def p_gate_op_4e0(self, _): - """ - gate_op : id '(' ')' error - """ - raise QasmError("Invalid bit list inside gate definition or" + " missing ';'") - - def p_gate_op_4e1(self, _): - """ - gate_op : id '(' error - """ - raise QasmError("Unmatched () for gate invocation inside gate" + " invocation.") - - def p_gate_op_5(self, program): - """ - gate_op : BARRIER id_list ';' - """ - program[0] = node.Barrier([program[2]]) - self.verify_bit_list(program[2]) - self.verify_distinct([program[2]]) - - def p_gate_op_5e(self, _): - """ - gate_op : BARRIER error - """ - raise QasmError("Invalid barrier inside gate definition.") - - # ---------------------------------------- - # opaque : OPAQUE id gate_scope bit_list - # | OPAQUE id gate_scope '(' ')' bit_list - # | OPAQUE id gate_scope '(' gate_id_list ')' bit_list - # - # These are like gate declarations only without a body. - # ---------------------------------------- - def p_opaque_0(self, program): - """ - opaque : OPAQUE id gate_scope bit_list - """ - # TODO: Review Opaque function - program[0] = node.Opaque([program[2], program[4]]) - if program[2].name in self.external_functions: - raise QasmError( - "OPAQUE names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - self.pop_scope() - self.update_symtab(program[0]) - - def p_opaque_1(self, program): - """ - opaque : OPAQUE id gate_scope '(' ')' bit_list - """ - program[0] = node.Opaque([program[2], program[6]]) - self.pop_scope() - self.update_symtab(program[0]) - - def p_opaque_2(self, program): - """ - opaque : OPAQUE id gate_scope '(' gate_id_list ')' bit_list - """ - program[0] = node.Opaque([program[2], program[5], program[7]]) - if program[2].name in self.external_functions: - raise QasmError( - "OPAQUE names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - self.pop_scope() - self.update_symtab(program[0]) - - def p_opaque_1e(self, _): - """ - opaque : OPAQUE id gate_scope '(' error - """ - raise QasmError("Poorly formed OPAQUE statement.") - - # ---------------------------------------- - # measure : MEASURE primary ASSIGN primary - # ---------------------------------------- - def p_measure(self, program): - """ - measure : MEASURE primary ASSIGN primary - """ - program[0] = node.Measure([program[2], program[4]]) - self.verify_reg(program[2], "qreg") - self.verify_reg(program[4], "creg") - - def p_measure_e(self, program): - """ - measure : MEASURE primary error - """ - raise QasmError("Illegal measure statement." + str(program[3].value)) - - # ---------------------------------------- - # barrier : BARRIER primary_list - # - # Errors are covered by handling errors in primary_list - # ---------------------------------------- - def p_barrier(self, program): - """ - barrier : BARRIER primary_list - """ - program[0] = node.Barrier([program[2]]) - self.verify_reg_list(program[2], "qreg") - self.verify_distinct([program[2]]) - - # ---------------------------------------- - # reset : RESET primary - # ---------------------------------------- - def p_reset(self, program): - """ - reset : RESET primary - """ - program[0] = node.Reset([program[2]]) - self.verify_reg(program[2], "qreg") - - # ---------------------------------------- - # IF '(' ID MATCHES NNINTEGER ')' quantum_op - # ---------------------------------------- - def p_if(self, program): - """ - if : IF '(' id MATCHES NNINTEGER ')' quantum_op - if : IF '(' id error - if : IF '(' id MATCHES error - if : IF '(' id MATCHES NNINTEGER error - if : IF error - """ - if len(program) == 3: - raise QasmError("Ill-formed IF statement. Perhaps a" + " missing '('?") - if len(program) == 5: - raise QasmError( - "Ill-formed IF statement. Expected '==', " + "received '" + str(program[4].value) - ) - if len(program) == 6: - raise QasmError( - "Ill-formed IF statement. Expected a number, " - + "received '" - + str(program[5].value) - ) - if len(program) == 7: - raise QasmError("Ill-formed IF statement, unmatched '('") - - if program[7].type == "if": - raise QasmError("Nested IF statements not allowed") - if program[7].type == "barrier": - raise QasmError("barrier not permitted in IF statement") - - program[0] = node.If([program[3], node.Int(program[5]), program[7]]) - - # ---------------------------------------- - # These are all the things you can have outside of a gate declaration - # quantum_op : unitary_op - # | opaque - # | measure - # | reset - # | barrier - # | if - # - # ---------------------------------------- - def p_quantum_op(self, program): - """ - quantum_op : unitary_op - | opaque - | measure - | barrier - | reset - | if - """ - program[0] = program[1] - - # ---------------------------------------- - # unary : NNINTEGER - # | REAL - # | PI - # | ID - # | '(' expression ')' - # | id '(' expression ')' - # - # We will trust 'expression' to throw before we have to handle it here - # ---------------------------------------- - def p_unary_0(self, program): - """ - unary : NNINTEGER - """ - program[0] = node.Int(program[1]) - - def p_unary_1(self, program): - """ - unary : REAL - """ - program[0] = node.Real(program[1]) - - def p_unary_2(self, program): - """ - unary : PI - """ - program[0] = node.Real(np.pi) - - def p_unary_3(self, program): - """ - unary : id - """ - program[0] = program[1] - - def p_unary_4(self, program): - """ - unary : '(' expression ')' - """ - program[0] = program[2] - - def p_unary_6(self, program): - """ - unary : id '(' expression ')' - """ - # note this is a semantic check, not syntactic - if program[1].name not in self.external_functions: - raise QasmError("Illegal external function call: ", str(program[1].name)) - program[0] = node.External([program[1], program[3]]) - - # ---------------------------------------- - # Prefix - # ---------------------------------------- - - def p_expression_1(self, program): - """ - expression : '-' expression %prec negative - | '+' expression %prec positive - """ - program[0] = node.Prefix([node.UnaryOperator(program[1]), program[2]]) - - def p_expression_0(self, program): - """ - expression : expression '*' expression - | expression '/' expression - | expression '+' expression - | expression '-' expression - | expression '^' expression - """ - program[0] = node.BinaryOp([node.BinaryOperator(program[2]), program[1], program[3]]) - - def p_expression_2(self, program): - """ - expression : unary - """ - program[0] = program[1] - - # ---------------------------------------- - # exp_list : exp - # | exp_list ',' exp - # ---------------------------------------- - def p_exp_list_0(self, program): - """ - exp_list : expression - """ - program[0] = node.ExpressionList([program[1]]) - - def p_exp_list_1(self, program): - """ - exp_list : exp_list ',' expression - """ - program[0] = program[1] - program[0].add_child(program[3]) - - def p_ignore(self, _): - """ - ignore : STRING - """ - # this should never hit but it keeps the insuppressible warnings at bay - pass - - def p_error(self, program): - # EOF is a special case because the stupid error token isn't placed - # on the stack - if not program: - raise QasmError("Error at end of file. " + "Perhaps there is a missing ';'") - - col = self.find_column(self.lexer.data, program) - print("Error near line", str(self.lexer.lineno), "Column", col) - - def find_column(self, input_, token): - """Compute the column. - - Input is the input text string. - token is a token instance. - """ - if token is None: - return 0 - last_cr = input_.rfind("\n", 0, token.lexpos) - last_cr = max(last_cr, 0) - column = (token.lexpos - last_cr) + 1 - return column - - def read_tokens(self): - """finds and reads the tokens.""" - try: - while True: - token = self.lexer.token() - - if not token: - break - - yield token - except QasmError as e: - print("Exception tokenizing qasm file:", e.msg) - - def parse_debug(self, val): - """Set the parse_deb field.""" - if val is True: - self.parse_deb = True - elif val is False: - self.parse_deb = False - else: - raise QasmError("Illegal debug value '" + str(val) + "' must be True or False.") - - def parse(self, data): - """Parse some data.""" - self.parser.parse(data, lexer=self.lexer, debug=self.parse_deb) - if self.qasm is None: - raise QasmError("Uncaught exception in parser; " + "see previous messages for details.") - return self.qasm - - def print_tree(self): - """Print parsed OPENQASM.""" - if self.qasm is not None: - self.qasm.to_string(0) - else: - print("No parsed qasm to print") - - def run(self, data): - """Parser runner. - - To use this module stand-alone. - """ - ast = self.parser.parse(data, debug=True) - self.parser.parse(data, debug=True) - ast.to_string(0) diff --git a/qiskit/qasm2/__init__.py b/qiskit/qasm2/__init__.py index e14ab420f380..485c210c0632 100644 --- a/qiskit/qasm2/__init__.py +++ b/qiskit/qasm2/__init__.py @@ -397,19 +397,6 @@ def add_one(x): serialisation format, and expanded its behaviour as Qiskit expanded. The new parser under all its defaults implements the specification more strictly. -The complete legacy code-paths are - -.. code-block:: python - - from qiskit.converters import ast_to_dag, dag_to_circuit - from qiskit.qasm import Qasm - - def from_qasm_file(path: str): - dag_to_circuit(ast_to_dag(Qasm(filename=path).parse())) - - def from_qasm_str(qasm_str: str): - dag_to_circuit(ast_to_dag(Qasm(data=qasm_str).parse())) - In particular, in the legacy importers: * the `include_path` is effectively: diff --git a/qiskit/tools/jupyter/library.py b/qiskit/tools/jupyter/library.py index 773416ad3a04..57a27ece8cdc 100644 --- a/qiskit/tools/jupyter/library.py +++ b/qiskit/tools/jupyter/library.py @@ -131,67 +131,6 @@ def properties_widget(circuit: QuantumCircuit) -> wid.VBox: return properties -@_optionals.HAS_PYGMENTS.require_in_call -@deprecate_func( - since="0.25.0", - additional_msg="This is unused by Qiskit, and no replacement will be publicly provided.", - package_name="qiskit-terra", -) -def qasm_widget(circuit: QuantumCircuit) -> wid.VBox: - """Generate an OpenQASM widget with header for a quantum circuit. - - Args: - circuit: Input quantum circuit. - - Returns: - Output widget. - """ - import pygments - from pygments.formatters import HtmlFormatter - from qiskit.qasm.pygments import QasmHTMLStyle, OpenQASMLexer - - qasm_code = circuit.qasm() - code = pygments.highlight(qasm_code, OpenQASMLexer(), HtmlFormatter()) - - html_style = HtmlFormatter(style=QasmHTMLStyle).get_style_defs(".highlight") - - code_style = ( - """ - - """ - % html_style - ) - - out = wid.HTML( - code_style + code, - layout=wid.Layout(max_height="500px", height="auto", overflow="scroll scroll"), - ) - - out_label = wid.HTML( - f"

OpenQASM

", - layout=wid.Layout(margin="0px 0px 10px 0px"), - ) - - qasm = wid.VBox( - children=[out_label, out], - layout=wid.Layout( - height="auto", max_height="500px", width="60%", margin="0px 0px 0px 20px" - ), - ) - - qasm._code_length = len(qasm_code.split("\n")) - return qasm - - @deprecate_func( since="0.25.0", additional_msg="This is unused by Qiskit, and no replacement will be publicly provided.", @@ -230,8 +169,7 @@ def circuit_library_widget(circuit: QuantumCircuit) -> None: Args: circuit: Input quantum circuit. """ - qasm_wid = qasm_widget(circuit) - sep_length = str(min(20 * qasm_wid._code_length, 495)) + sep_length = str(min(20, 495)) # The separator widget sep = wid.HTML( @@ -239,7 +177,7 @@ def circuit_library_widget(circuit: QuantumCircuit) -> None: layout=wid.Layout(height="auto", max_height="495px", margin="40px 0px 0px 20px"), ) bottom = wid.HBox( - children=[properties_widget(circuit), sep, qasm_widget(circuit)], + children=[properties_widget(circuit), sep], layout=wid.Layout(max_height="550px", height="auto"), ) diff --git a/releasenotes/notes/remove-legacy-qasm2-parser-53ad3f1817fd68cc.yaml b/releasenotes/notes/remove-legacy-qasm2-parser-53ad3f1817fd68cc.yaml new file mode 100644 index 000000000000..d3a581761060 --- /dev/null +++ b/releasenotes/notes/remove-legacy-qasm2-parser-53ad3f1817fd68cc.yaml @@ -0,0 +1,34 @@ +--- +upgrade: + - | + The legacy OpenQASM 2 parser module previously present in ``qiskit.qasm`` has + been removed. It was marked as deprecated in Qiskit 0.46.0 release. The + OpenQASM 2 parser has been superseded by the :mod:`qiskit.qasm2` module which + provides a faster more correct parser for QASM2. + - | + The ``qiskit.converters.ast_to_dag`` function has been removed. It + previously was used to convert the abstract syntax tree generated by the + legacy OpenQASM 2 parser (in the ``qiskit.qasm`` module which no longer exists) + and convert that directly to a :class:`.DAGCircuit`. This function was + marked as deprecated in the Qiskit 0.46.0 release. As the legacy + OpenQASM 2 parser has been removed this function no longer serves a purpose. + If you were previously using this, you can instead parse your OpenQASM 2 files + into a :class:`.QuantumCircuit` using the + :meth:`.QuantumCircuit.from_qasm_file` or + :meth:`.QuantumCircuit.from_qasm_str` constructor methods and then + converting that :class:`.QuantumCircuit` into a :class:`.DAGCircuit` with + :func:`.circuit_to_dag`. + - | + Removed the ``QuantumCircuit.qasm()`` method to generate a OpenQASM 2 + representation of the :class:`.QuantumCircuit` object. Instead the + :func:`.qasm2.dump` or :func:`.qasm2.dumps` functions should be used. This + function was marked as deprecated in the 0.46.0 release. If you were using + the ``QuantumCircuit.qasm()`` method to generate pygments formatted output + you should instead look at the standalone ``openqasm-pygments`` package + to provide this functionality. + - | + Removed the ``qiskit.tools.jupyter.library.qasm_widget`` function which + was used to visualize qasm strings in a jupyter notebook. This function + was marked as deprecated as part of the Qiskit 0.44.0 release. The function + was originally used for building documentation but hasn't been used in some + time and has been removed from Qiskit. diff --git a/requirements-optional.txt b/requirements-optional.txt index 6afa25271ab9..6c523a4937f3 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -17,7 +17,6 @@ ipywidgets>=7.3.0 matplotlib>=3.3 pillow>=4.2.1 pydot -pygments>=2.4 pylatexenc>=1.4 seaborn>=0.9.0 diff --git a/requirements.txt b/requirements.txt index 31206e951128..6402e9a6549f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ rustworkx>=0.13.0 numpy>=1.17,<2 -ply>=3.10 psutil>=5 scipy>=1.5 sympy>=1.3 diff --git a/setup.py b/setup.py index 55950efef410..62e6f43bcbc6 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,6 @@ "pillow>=4.2.1", "pylatexenc>=1.4", "seaborn>=0.9.0", - "pygments>=2.4", ] z3_requirements = [ "z3-solver>=4.7", diff --git a/test/benchmarks/converters.py b/test/benchmarks/converters.py index 041e40dd6de1..d99822fa8016 100644 --- a/test/benchmarks/converters.py +++ b/test/benchmarks/converters.py @@ -14,7 +14,6 @@ # pylint: disable=attribute-defined-outside-init,unsubscriptable-object from qiskit import converters -from qiskit import qasm from .utils import random_circuit @@ -38,7 +37,6 @@ def setup(self, n_qubits, depth): raise NotImplementedError self.qc = random_circuit(n_qubits, depth, measure=True, conditional=True, seed=seed) self.dag = converters.circuit_to_dag(self.qc) - self.qasm = qasm.Qasm(data=self.qc.qasm()).parse() def time_circuit_to_dag(self, *_): converters.circuit_to_dag(self.qc) @@ -48,6 +46,3 @@ def time_circuit_to_instruction(self, *_): def time_dag_to_circuit(self, *_): converters.dag_to_circuit(self.dag) - - def time_ast_to_circuit(self, *_): - converters.ast_to_dag(self.qasm) diff --git a/test/python/basicaer/test_qasm_simulator.py b/test/python/basicaer/test_qasm_simulator.py index 90bb10149943..6deb874d9c82 100644 --- a/test/python/basicaer/test_qasm_simulator.py +++ b/test/python/basicaer/test_qasm_simulator.py @@ -25,6 +25,7 @@ from qiskit.compiler import transpile, assemble from qiskit.providers.basicaer import QasmSimulatorPy from qiskit.test import providers +from qiskit.qasm2 import dumps class StreamHandlerRaiseException(StreamHandler): @@ -306,7 +307,7 @@ def test_teleport(self): "1": data["1 0 0"] + data["1 1 0"] + data["1 0 1"] + data["1 1 1"], } self.log.info("test_teleport: circuit:") - self.log.info(circuit.qasm()) + self.log.info(dumps(circuit)) self.log.info("test_teleport: data %s", data) self.log.info("test_teleport: alice %s", alice) self.log.info("test_teleport: bob %s", bob) diff --git a/test/python/circuit/library/test_permutation.py b/test/python/circuit/library/test_permutation.py index 25bfa0bfae49..aacc5425d74b 100644 --- a/test/python/circuit/library/test_permutation.py +++ b/test/python/circuit/library/test_permutation.py @@ -25,6 +25,7 @@ from qiskit.circuit.library import Permutation, PermutationGate from qiskit.quantum_info import Operator from qiskit.qpy import dump, load +from qiskit.qasm2 import dumps class TestPermutationLibrary(QiskitTestCase): @@ -160,9 +161,9 @@ def test_qasm(self): "gate permutation__2_4_3_0_1_ q0,q1,q2,q3,q4 { swap q2,q3; swap q1,q4; swap q0,q3; }\n" "qreg q0[5];\n" "permutation__2_4_3_0_1_ q0[0],q0[1],q0[2],q0[3],q0[4];\n" - "h q0[0];\n" + "h q0[0];" ) - self.assertEqual(expected_qasm, circuit.qasm()) + self.assertEqual(expected_qasm, dumps(circuit)) def test_qpy(self): """Test qpy for circuits with permutations.""" diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py index 5b27f968d021..7102df18bf68 100644 --- a/test/python/circuit/test_circuit_qasm.py +++ b/test/python/circuit/test_circuit_qasm.py @@ -20,7 +20,8 @@ from qiskit.test import QiskitTestCase from qiskit.circuit import Parameter, Qubit, Clbit, Gate from qiskit.circuit.library import C3SXGate, CCZGate, CSGate, CSdgGate, PermutationGate -from qiskit.qasm.exceptions import QasmError +from qiskit.qasm2.exceptions import QASM2Error as QasmError +from qiskit.qasm2 import dumps # Regex pattern to match valid OpenQASM identifiers VALID_QASM2_IDENTIFIER = re.compile("[a-z][a-zA-Z_0-9]*") @@ -69,8 +70,8 @@ def test_circuit_qasm(self): barrier qr1[0],qr2[0],qr2[1]; measure qr1[0] -> cr[0]; measure qr2[0] -> cr[1]; -measure qr2[1] -> cr[2];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +measure qr2[1] -> cr[2];""" + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_with_composite_circuit(self): """Test circuit qasm() method when a composite circuit instruction @@ -103,8 +104,8 @@ def test_circuit_qasm_with_composite_circuit(self): barrier qr[0],qr[1]; composite_circ qr[0],qr[1]; measure qr[0] -> cr[0]; -measure qr[1] -> cr[1];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +measure qr[1] -> cr[1];""" + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_with_multiple_same_composite_circuits(self): """Test circuit qasm() method when a composite circuit is added @@ -139,8 +140,8 @@ def test_circuit_qasm_with_multiple_same_composite_circuits(self): composite_circ qr[0],qr[1]; composite_circ qr[0],qr[1]; measure qr[0] -> cr[0]; -measure qr[1] -> cr[1];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +measure qr[1] -> cr[1];""" + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_with_multiple_composite_circuits_with_same_name(self): """Test circuit qasm() method when multiple composite circuit instructions @@ -175,10 +176,10 @@ def test_circuit_qasm_with_multiple_composite_circuits_with_same_name(self): qreg qr[1]; my_gate qr[0]; my_gate_{1} qr[0]; -my_gate_{0} qr[0];\n""".format( +my_gate_{0} qr[0];""".format( my_gate_inst3_id, my_gate_inst2_id ) - self.assertEqual(circuit.qasm(), expected_qasm) + self.assertEqual(dumps(circuit), expected_qasm) def test_circuit_qasm_with_composite_circuit_with_children_composite_circuit(self): """Test circuit qasm() method when composite circuits with children @@ -205,16 +206,16 @@ def test_circuit_qasm_with_composite_circuit_with_children_composite_circuit(sel gate parent_circ q0,q1,q2 { child_circ q0,q1; h q2; } gate grandparent_circ q0,q1,q2,q3 { parent_circ q0,q1,q2; x q3; } qreg q[4]; -grandparent_circ q[0],q[1],q[2],q[3];\n""" +grandparent_circ q[0],q[1],q[2],q[3];""" - self.assertEqual(qc.qasm(), expected_qasm) + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_pi(self): """Test circuit qasm() method with pi params.""" circuit = QuantumCircuit(2) circuit.cz(0, 1) circuit.u(2 * pi, 3 * pi, -5 * pi, 0) - qasm_str = circuit.qasm() + qasm_str = dumps(circuit) circuit2 = QuantumCircuit.from_qasm_str(qasm_str) self.assertEqual(circuit, circuit2) @@ -227,10 +228,10 @@ def test_circuit_qasm_with_composite_circuit_with_one_param(self): gate nG0(param0) q0 { h q0; } qreg q[3]; creg c[3]; -nG0(pi) q[0];\n""" +nG0(pi) q[0];""" qc = QuantumCircuit.from_qasm_str(original_str) - self.assertEqual(original_str, qc.qasm()) + self.assertEqual(original_str, dumps(qc)) def test_circuit_qasm_with_composite_circuit_with_many_params_and_qubits(self): """Test circuit qasm() method when a composite circuit instruction @@ -243,10 +244,10 @@ def test_circuit_qasm_with_composite_circuit_with_many_params_and_qubits(self): qreg r[3]; creg c[3]; creg d[3]; -nG0(pi,pi/2) q[0],r[0];\n""" +nG0(pi,pi/2) q[0],r[0];""" qc = QuantumCircuit.from_qasm_str(original_str) - self.assertEqual(original_str, qc.qasm()) + self.assertEqual(original_str, dumps(qc)) def test_c3sxgate_roundtrips(self): """Test that C3SXGate correctly round trips. @@ -256,12 +257,11 @@ def test_c3sxgate_roundtrips(self): resolution issues.""" qc = QuantumCircuit(4) qc.append(C3SXGate(), qc.qubits, []) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; -c3sqrtx q[0],q[1],q[2],q[3]; -""" +c3sqrtx q[0],q[1],q[2],q[3];""" self.assertEqual(qasm, expected) parsed = QuantumCircuit.from_qasm_str(qasm) self.assertIsInstance(parsed.data[0].operation, C3SXGate) @@ -275,39 +275,36 @@ def test_cczgate_qasm(self): """Test that CCZ dumps definition as a non-qelib1 gate.""" qc = QuantumCircuit(3) qc.append(CCZGate(), qc.qubits, []) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate ccz q0,q1,q2 { h q2; ccx q0,q1,q2; h q2; } qreg q[3]; -ccz q[0],q[1],q[2]; -""" +ccz q[0],q[1],q[2];""" self.assertEqual(qasm, expected) def test_csgate_qasm(self): """Test that CS dumps definition as a non-qelib1 gate.""" qc = QuantumCircuit(2) qc.append(CSGate(), qc.qubits, []) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate cs q0,q1 { p(pi/4) q0; cx q0,q1; p(-pi/4) q1; cx q0,q1; p(pi/4) q1; } qreg q[2]; -cs q[0],q[1]; -""" +cs q[0],q[1];""" self.assertEqual(qasm, expected) def test_csdggate_qasm(self): """Test that CSdg dumps definition as a non-qelib1 gate.""" qc = QuantumCircuit(2) qc.append(CSdgGate(), qc.qubits, []) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate csdg q0,q1 { p(-pi/4) q0; cx q0,q1; p(pi/4) q1; cx q0,q1; p(-pi/4) q1; } qreg q[2]; -csdg q[0],q[1]; -""" +csdg q[0],q[1];""" self.assertEqual(qasm, expected) def test_rzxgate_qasm(self): @@ -315,14 +312,13 @@ def test_rzxgate_qasm(self): qc = QuantumCircuit(2) qc.rzx(0, 0, 1) qc.rzx(pi / 2, 1, 0) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate rzx(param0) q0,q1 { h q1; cx q0,q1; rz(param0) q1; cx q0,q1; h q1; } qreg q[2]; rzx(0) q[0],q[1]; -rzx(pi/2) q[1],q[0]; -""" +rzx(pi/2) q[1],q[0];""" self.assertEqual(qasm, expected) def test_ecrgate_qasm(self): @@ -330,28 +326,26 @@ def test_ecrgate_qasm(self): qc = QuantumCircuit(2) qc.ecr(0, 1) qc.ecr(1, 0) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate rzx(param0) q0,q1 { h q1; cx q0,q1; rz(param0) q1; cx q0,q1; h q1; } gate ecr q0,q1 { rzx(pi/4) q0,q1; x q0; rzx(-pi/4) q0,q1; } qreg q[2]; ecr q[0],q[1]; -ecr q[1],q[0]; -""" +ecr q[1],q[0];""" self.assertEqual(qasm, expected) def test_unitary_qasm(self): """Test that UnitaryGate can be dumped to OQ2 correctly.""" qc = QuantumCircuit(1) qc.unitary([[1, 0], [0, 1]], 0) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate unitary q0 { u(0,0,0) q0; } qreg q[1]; -unitary q[0]; -""" +unitary q[0];""" self.assertEqual(qasm, expected) def test_multiple_unitary_qasm(self): @@ -363,7 +357,7 @@ def test_multiple_unitary_qasm(self): qc.unitary([[1, 0], [0, 1]], 0) qc.unitary([[0, 1], [1, 0]], 1) qc.append(custom.to_gate(), [0], []) - qasm = qc.qasm() + qasm = dumps(qc) expected = re.compile( r"""OPENQASM 2.0; include "qelib1.inc"; @@ -374,8 +368,7 @@ def test_multiple_unitary_qasm(self): qreg q\[2\]; unitary q\[0\]; (?P=u1) q\[1\]; -custom q\[0\]; -""", +custom q\[0\];""", re.MULTILINE, ) self.assertRegex(qasm, expected) @@ -386,7 +379,7 @@ def test_unbound_circuit_raises(self): theta = Parameter("θ") qc.rz(theta, 0) with self.assertRaises(QasmError): - qc.qasm() + dumps(qc) def test_gate_qasm_with_ctrl_state(self): """Test gate qasm() with controlled gate that has ctrl_state setting.""" @@ -394,7 +387,7 @@ def test_gate_qasm_with_ctrl_state(self): qc = QuantumCircuit(2) qc.ch(0, 1, ctrl_state=0) - qasm_str = qc.qasm() + qasm_str = dumps(qc) self.assertEqual(Operator(qc), Operator(QuantumCircuit.from_qasm_str(qasm_str))) def test_circuit_qasm_with_mcx_gate(self): @@ -410,8 +403,8 @@ def test_circuit_qasm_with_mcx_gate(self): include "qelib1.inc"; gate mcx q0,q1,q2,q3 { h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; } qreg q[4]; -mcx q[0],q[1],q[2],q[3];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +mcx q[0],q[1],q[2],q[3];""" + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_with_mcx_gate_variants(self): """Test circuit qasm() method with MCXGrayCode, MCXRecursive, MCXVChain""" @@ -435,9 +428,9 @@ def test_circuit_qasm_with_mcx_gate_variants(self): qreg q[9]; mcx_gray q[0],q[1],q[2],q[3],q[4],q[5]; mcx_recursive q[0],q[1],q[2],q[3],q[4],q[5],q[6]; -mcx_vchain q[0],q[1],q[2],q[3],q[4],q[5],q[6],q[7],q[8];\n""" +mcx_vchain q[0],q[1],q[2],q[3],q[4],q[5],q[6],q[7],q[8];""" - self.assertEqual(qc.qasm(), expected_qasm) + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_with_registerless_bits(self): """Test that registerless bits do not have naming collisions in their registers.""" @@ -446,7 +439,7 @@ def test_circuit_qasm_with_registerless_bits(self): # Match a 'qreg identifier[3];'-like QASM register declaration. register_regex = re.compile(r"\s*[cq]reg\s+(\w+)\s*\[\d+\]\s*", re.M) qasm_register_names = set() - for statement in qc.qasm().split(";"): + for statement in dumps(qc).split(";"): match = register_regex.match(statement) if match: qasm_register_names.add(match.group(1)) @@ -462,7 +455,7 @@ def test_circuit_qasm_with_registerless_bits(self): for generated_name in generated_names: qc.add_register(QuantumRegister(1, name=generated_name)) qasm_register_names = set() - for statement in qc.qasm().split(";"): + for statement in dumps(qc).split(";"): match = register_regex.match(statement) if match: qasm_register_names.add(match.group(1)) @@ -498,9 +491,9 @@ def test_circuit_qasm_with_repeated_instruction_names(self): h q[0]; x q[1]; custom q[0]; -custom_{id(gate2)} q[1],q[0];\n""" +custom_{id(gate2)} q[1],q[0];""" # Check qasm() produced the correct string - self.assertEqual(expected_qasm, qc.qasm()) + self.assertEqual(expected_qasm, dumps(qc)) # Check instruction names were not changed by qasm() names = ["h", "x", "custom", "custom"] for idx, instruction in enumerate(qc._data): @@ -539,12 +532,11 @@ def test_circuit_qasm_with_invalid_identifiers(self): "qreg q[2];", "gate_A___ q[0];", "invalid_name_ q[1],q[0];", - "", ] ) # Check qasm() produces the correct string - self.assertEqual(expected_qasm, qc.qasm()) + self.assertEqual(expected_qasm, dumps(qc)) # Check instruction names were not changed by qasm() names = ["A[$]", "invalid[name]"] @@ -568,7 +560,7 @@ def test_circuit_qasm_with_duplicate_invalid_identifiers(self): # Check qasm is correctly produced names = set() - for match in re.findall(r"gate (\S+)", base.qasm()): + for match in re.findall(r"gate (\S+)", dumps(base)): self.assertTrue(VALID_QASM2_IDENTIFIER.fullmatch(match)) names.add(match) self.assertEqual(len(names), 2) @@ -584,15 +576,14 @@ def test_circuit_qasm_escapes_register_names(self): qc = QuantumCircuit(QuantumRegister(2, "?invalid"), QuantumRegister(2, "!invalid")) qc.cx(0, 1) qc.cx(2, 3) - qasm = qc.qasm() + qasm = dumps(qc) match = re.fullmatch( rf"""OPENQASM 2.0; include "qelib1.inc"; qreg ({VALID_QASM2_IDENTIFIER.pattern})\[2\]; qreg ({VALID_QASM2_IDENTIFIER.pattern})\[2\]; cx \1\[0\],\1\[1\]; -cx \2\[0\],\2\[1\]; -""", +cx \2\[0\],\2\[1\];""", qasm, ) self.assertTrue(match) @@ -604,14 +595,13 @@ def test_circuit_qasm_escapes_reserved(self): gate = Gate("gate", 1, []) gate.definition = QuantumCircuit(1) qc.append(gate, [qc.qubits[0]]) - qasm = qc.qasm() + qasm = dumps(qc) match = re.fullmatch( rf"""OPENQASM 2.0; include "qelib1.inc"; gate ({VALID_QASM2_IDENTIFIER.pattern}) q0 {{ }} qreg ({VALID_QASM2_IDENTIFIER.pattern})\[1\]; -\1 \2\[0\]; -""", +\1 \2\[0\];""", qasm, ) self.assertTrue(match) @@ -632,8 +622,8 @@ def test_circuit_qasm_with_double_precision_rotation_angle(self): qreg q[1]; p(0.123456789) q[0]; p(9.869604401089358) q[0]; -p(51.26548245743669) q[0];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +p(51.26548245743669) q[0];""" + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_with_rotation_angles_close_to_pi(self): """Test that qasm() properly rounds values closer than 1e-12 to pi.""" @@ -645,8 +635,8 @@ def test_circuit_qasm_with_rotation_angles_close_to_pi(self): include "qelib1.inc"; qreg q[1]; p(3.141592653599793) q[0]; -p(pi) q[0];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +p(pi) q[0];""" + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_raises_on_single_bit_condition(self): """OpenQASM 2 can't represent single-bit conditions, so test that a suitable error is @@ -655,7 +645,7 @@ def test_circuit_raises_on_single_bit_condition(self): qc.x(0).c_if(0, True) with self.assertRaisesRegex(QasmError, "OpenQASM 2 can only condition on registers"): - qc.qasm() + dumps(qc) def test_circuit_raises_invalid_custom_gate_no_qubits(self): """OpenQASM 2 exporter of custom gates with no qubits. @@ -665,7 +655,7 @@ def test_circuit_raises_invalid_custom_gate_no_qubits(self): legit_circuit.append(empty_circuit) with self.assertRaisesRegex(QasmError, "acts on zero qubits"): - legit_circuit.qasm() + dumps(legit_circuit) def test_circuit_raises_invalid_custom_gate_clbits(self): """OpenQASM 2 exporter of custom instruction. @@ -679,7 +669,7 @@ def test_circuit_raises_invalid_custom_gate_clbits(self): qc.append(custom_instruction, [0, 1], [0, 1]) with self.assertRaisesRegex(QasmError, "acts on 2 classical bits"): - qc.qasm() + dumps(qc) def test_circuit_qasm_with_permutations(self): """Test circuit qasm() method with Permutation gates.""" @@ -691,8 +681,8 @@ def test_circuit_qasm_with_permutations(self): include "qelib1.inc"; gate permutation__2_1_0_ q0,q1,q2 { swap q0,q2; } qreg q[4]; -permutation__2_1_0_ q[0],q[1],q[2];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +permutation__2_1_0_ q[0],q[1],q[2];""" + self.assertEqual(dumps(qc), expected_qasm) def test_multiple_permutation(self): """Test that multiple PermutationGates can be added to a circuit.""" @@ -704,7 +694,7 @@ def test_multiple_permutation(self): qc.append(PermutationGate([2, 1, 0]), [0, 1, 2], []) qc.append(PermutationGate([1, 2, 0]), [0, 1, 2], []) qc.append(custom.to_gate(), [1, 3, 2], []) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate permutation__2_1_0_ q0,q1,q2 { swap q0,q2; } @@ -714,8 +704,7 @@ def test_multiple_permutation(self): qreg q[4]; permutation__2_1_0_ q[0],q[1],q[2]; permutation__1_2_0_ q[0],q[1],q[2]; -custom q[1],q[3],q[2]; -""" +custom q[1],q[3],q[2];""" self.assertEqual(qasm, expected) def test_circuit_qasm_with_reset(self): @@ -727,8 +716,8 @@ def test_circuit_qasm_with_reset(self): include "qelib1.inc"; qreg q[2]; reset q[0]; -reset q[1];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +reset q[1];""" + self.assertEqual(dumps(qc), expected_qasm) def test_nested_gate_naming_clashes(self): """Test that gates that have naming clashes but only appear in the body of another gate @@ -755,7 +744,7 @@ def _define(self): qc = QuantumCircuit(1) qc.append(Outer(1.0), [0], []) qc.append(Outer(2.0), [0], []) - qasm = qc.qasm() + qasm = dumps(qc) expected = re.compile( r"""OPENQASM 2\.0; @@ -766,8 +755,7 @@ def _define(self): gate (?Pouter_[0-9]*)\(param0\) q0 { (?P=inner1)\(2\.0\) q0; } qreg q\[1\]; outer\(1\.0\) q\[0\]; -(?P=outer1)\(2\.0\) q\[0\]; -""", +(?P=outer1)\(2\.0\) q\[0\];""", re.MULTILINE, ) self.assertRegex(qasm, expected) @@ -782,7 +770,7 @@ def test_opaque_output(self): qc.append(Gate("my_a", 1, []), [1]) qc.append(Gate("my_b", 2, [1.0]), [1, 0]) qc.append(custom.to_gate(), [0], []) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; opaque my_a q0; @@ -793,8 +781,7 @@ def test_opaque_output(self): my_a q[0]; my_a q[1]; my_b(1.0) q[1],q[0]; -custom q[0]; -""" +custom q[0];""" self.assertEqual(qasm, expected) def test_sequencial_inner_gates_with_same_name(self): @@ -824,10 +811,9 @@ def test_sequencial_inner_gates_with_same_name(self): a_{gate_a_id} q[0],q[1],q[2]; z q[0]; z q[1]; -z q[2]; -""" +z q[2];""" - self.assertEqual(qc.qasm(), expected_output) + self.assertEqual(dumps(qc), expected_output) def test_empty_barrier(self): """Test that a blank barrier statement in _Qiskit_ acts over all qubits, while an explicitly @@ -842,9 +828,8 @@ def test_empty_barrier(self): include "qelib1.inc"; qreg qr1[2]; qreg qr2[3]; -barrier qr1[0],qr1[1],qr2[0],qr2[1],qr2[2]; -""" - self.assertEqual(qc.qasm(), expected) +barrier qr1[0],qr1[1],qr2[0],qr2[1],qr2[2];""" + self.assertEqual(dumps(qc), expected) def test_small_angle_valid(self): """Test that small angles do not get converted to invalid OQ2 floating-point values.""" @@ -856,9 +841,8 @@ def test_small_angle_valid(self): OPENQASM 2.0; include "qelib1.inc"; qreg q[1]; -rx(1.e-06) q[0]; -""" - self.assertEqual(qc.qasm(), expected) +rx(1.e-06) q[0];""" + self.assertEqual(dumps(qc), expected) if __name__ == "__main__": diff --git a/test/python/circuit/test_circuit_registers.py b/test/python/circuit/test_circuit_registers.py index d6a1a63cb469..ca0ce6d0bed3 100644 --- a/test/python/circuit/test_circuit_registers.py +++ b/test/python/circuit/test_circuit_registers.py @@ -26,6 +26,7 @@ ) from qiskit.circuit.exceptions import CircuitError from qiskit.test import QiskitTestCase +from qiskit.qasm2 import dumps class TestCircuitRegisters(QiskitTestCase): @@ -284,7 +285,7 @@ def test_apply_barrier_to_slice(self): num_qubits = 2 qc = QuantumCircuit(qr, cr) qc.barrier(qr[0:num_qubits]) - self.log.info(qc.qasm()) + self.log.info(dumps(qc)) self.assertEqual(len(qc.data), 1) self.assertEqual(qc.data[0].operation.name, "barrier") self.assertEqual(len(qc.data[0].qubits), num_qubits) diff --git a/test/python/circuit/test_unitary.py b/test/python/circuit/test_unitary.py index 27367372f329..26f3eb3fea2e 100644 --- a/test/python/circuit/test_unitary.py +++ b/test/python/circuit/test_unitary.py @@ -25,6 +25,7 @@ from qiskit.quantum_info.random import random_unitary from qiskit.quantum_info.operators import Operator from qiskit.transpiler.passes import CXCancellation +from qiskit.qasm2 import dumps class TestUnitaryGate(QiskitTestCase): @@ -79,7 +80,7 @@ def test_1q_unitary(self): qc.x(qr[0]) qc.append(UnitaryGate(matrix), [qr[0]]) # test of qasm output - self.log.info(qc.qasm()) + self.log.info(dumps(qc)) # test of text drawer self.log.info(qc) dag = circuit_to_dag(qc) @@ -105,7 +106,7 @@ def test_2q_unitary(self): passman.append(CXCancellation()) qc2 = passman.run(qc) # test of qasm output - self.log.info(qc2.qasm()) + self.log.info(dumps(qc2)) # test of text drawer self.log.info(qc2) dag = circuit_to_dag(qc) @@ -221,9 +222,9 @@ def test_qasm_unitary_only_one_def(self): "qreg q0[2];\ncreg c0[1];\n" "x q0[0];\n" "unitary q0[0];\n" - "unitary q0[1];\n" + "unitary q0[1];" ) - self.assertEqual(expected_qasm, qc.qasm()) + self.assertEqual(expected_qasm, dumps(qc)) def test_qasm_unitary_twice(self): """test that a custom unitary can be converted to qasm and that if @@ -245,10 +246,10 @@ def test_qasm_unitary_twice(self): "qreg q0[2];\ncreg c0[1];\n" "x q0[0];\n" "unitary q0[0];\n" - "unitary q0[1];\n" + "unitary q0[1];" ) - self.assertEqual(expected_qasm, qc.qasm()) - self.assertEqual(expected_qasm, qc.qasm()) + self.assertEqual(expected_qasm, dumps(qc)) + self.assertEqual(expected_qasm, dumps(qc)) def test_qasm_2q_unitary(self): """test that a 2 qubit custom unitary can be converted to qasm""" @@ -270,9 +271,9 @@ def test_qasm_2q_unitary(self): "creg c0[1];\n" "x q0[0];\n" "unitary q0[0],q0[1];\n" - "unitary q0[1],q0[0];\n" + "unitary q0[1],q0[0];" ) - self.assertEqual(expected_qasm, qc.qasm()) + self.assertEqual(expected_qasm, dumps(qc)) def test_qasm_unitary_noop(self): """Test that an identity unitary can be converted to OpenQASM 2""" @@ -283,9 +284,9 @@ def test_qasm_unitary_noop(self): 'include "qelib1.inc";\n' "gate unitary q0,q1,q2 { }\n" "qreg q0[3];\n" - "unitary q0[0],q0[1],q0[2];\n" + "unitary q0[0],q0[1],q0[2];" ) - self.assertEqual(expected_qasm, qc.qasm()) + self.assertEqual(expected_qasm, dumps(qc)) def test_unitary_decomposition(self): """Test decomposition for unitary gates over 2 qubits.""" diff --git a/test/python/compiler/test_compiler.py b/test/python/compiler/test_compiler.py index 910d1e5866fe..627d5310531f 100644 --- a/test/python/compiler/test_compiler.py +++ b/test/python/compiler/test_compiler.py @@ -24,6 +24,7 @@ from qiskit.test import QiskitTestCase from qiskit.providers.fake_provider import FakeRueschlikon, FakeTenerife from qiskit.qobj import QasmQobj +from qiskit.qasm2 import dumps class TestCompiler(QiskitTestCase): @@ -100,8 +101,8 @@ def test_compile_coupling_map(self): ) job = backend.run(qc_b, shots=shots, seed_simulator=88) result = job.result() - qasm_to_check = qc.qasm() - self.assertEqual(len(qasm_to_check), 173) + qasm_to_check = dumps(qc) + self.assertEqual(len(qasm_to_check), 172) counts = result.get_counts(qc) target = {"000": shots / 2, "111": shots / 2} diff --git a/test/python/converters/test_ast_to_dag.py b/test/python/converters/test_ast_to_dag.py deleted file mode 100644 index 490465b5967b..000000000000 --- a/test/python/converters/test_ast_to_dag.py +++ /dev/null @@ -1,67 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tests for the converters.""" - -import os -import unittest - -from qiskit.converters import ast_to_dag, circuit_to_dag -from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit -from qiskit import qasm -from qiskit.test import QiskitTestCase - - -class TestAstToDag(QiskitTestCase): - """Test AST to DAG.""" - - def setUp(self): - super().setUp() - qr = QuantumRegister(3) - cr = ClassicalRegister(3) - self.circuit = QuantumCircuit(qr, cr) - self.circuit.ccx(qr[0], qr[1], qr[2]) - self.circuit.measure(qr, cr) - self.dag = circuit_to_dag(self.circuit) - - def test_from_ast_to_dag(self): - """Test Unroller.execute()""" - qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") - ast = qasm.Qasm(os.path.join(qasm_dir, "example.qasm")).parse() - dag_circuit = ast_to_dag(ast) - expected_result = """\ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[3]; -qreg r[3]; -creg c[3]; -creg d[3]; -h q[0]; -h q[1]; -h q[2]; -cx q[0],r[0]; -cx q[1],r[1]; -cx q[2],r[2]; -barrier q[0],q[1],q[2]; -measure q[0] -> c[0]; -measure q[1] -> c[1]; -measure q[2] -> c[2]; -measure r[0] -> d[0]; -measure r[1] -> d[1]; -measure r[2] -> d[2]; -""" - expected_dag = circuit_to_dag(QuantumCircuit.from_qasm_str(expected_result)) - self.assertEqual(dag_circuit, expected_dag) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/test/python/qasm2/test_circuit_methods.py b/test/python/qasm2/test_circuit_methods.py index 9027fb4d54b9..e6b35e582ccb 100644 --- a/test/python/qasm2/test_circuit_methods.py +++ b/test/python/qasm2/test_circuit_methods.py @@ -21,6 +21,7 @@ from qiskit.test import QiskitTestCase from qiskit.transpiler.passes import Unroller from qiskit.converters.circuit_to_dag import circuit_to_dag +from qiskit.qasm2 import dumps class LoadFromQasmTest(QiskitTestCase): @@ -254,18 +255,15 @@ def test_qasm_example_file(self): def test_qasm_qas_string_order(self): """Test that gates are returned in qasm in ascending order.""" - expected_qasm = ( - "\n".join( - [ - "OPENQASM 2.0;", - 'include "qelib1.inc";', - "qreg q[3];", - "h q[0];", - "h q[1];", - "h q[2];", - ] - ) - + "\n" + expected_qasm = "\n".join( + [ + "OPENQASM 2.0;", + 'include "qelib1.inc";', + "qreg q[3];", + "h q[0];", + "h q[1];", + "h q[2];", + ] ) qasm_string = """OPENQASM 2.0; include "qelib1.inc"; @@ -273,7 +271,7 @@ def test_qasm_qas_string_order(self): h q;""" q_circuit = QuantumCircuit.from_qasm_str(qasm_string) - self.assertEqual(q_circuit.qasm(), expected_qasm) + self.assertEqual(dumps(q_circuit), expected_qasm) def test_from_qasm_str_custom_gate1(self): """Test load custom gates (simple case)""" diff --git a/test/python/qasm2/test_legacy_importer.py b/test/python/qasm2/test_legacy_importer.py deleted file mode 100644 index dea4d53f8fef..000000000000 --- a/test/python/qasm2/test_legacy_importer.py +++ /dev/null @@ -1,508 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - - -"""Test cases for the legacy OpenQASM 2 parser.""" - -# pylint: disable=missing-function-docstring - - -import os - -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister -from qiskit.circuit import Gate, Parameter -from qiskit.converters import ast_to_dag, dag_to_circuit -from qiskit.exceptions import QiskitError -from qiskit.qasm import Qasm -from qiskit.test import QiskitTestCase -from qiskit.transpiler.passes import Unroller -from qiskit.converters.circuit_to_dag import circuit_to_dag - - -def from_qasm_str(qasm_str): - return dag_to_circuit(ast_to_dag(Qasm(data=qasm_str).parse())) - - -def from_qasm_file(path): - return dag_to_circuit(ast_to_dag(Qasm(filename=path).parse())) - - -class LoadFromQasmTest(QiskitTestCase): - """Test circuit.from_qasm_* set of methods.""" - - def setUp(self): - super().setUp() - self.qasm_file_name = "entangled_registers.qasm" - self.qasm_dir = os.path.join( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm" - ) - self.qasm_file_path = os.path.join(self.qasm_dir, self.qasm_file_name) - - def test_qasm_file(self): - """ - Test qasm_file and get_circuit. - - If all is correct we should get the qasm file loaded in _qasm_file_path - """ - q_circuit = from_qasm_file(self.qasm_file_path) - qr_a = QuantumRegister(4, "a") - qr_b = QuantumRegister(4, "b") - cr_c = ClassicalRegister(4, "c") - cr_d = ClassicalRegister(4, "d") - q_circuit_2 = QuantumCircuit(qr_a, qr_b, cr_c, cr_d) - q_circuit_2.h(qr_a) - q_circuit_2.cx(qr_a, qr_b) - q_circuit_2.barrier(qr_a) - q_circuit_2.barrier(qr_b) - q_circuit_2.measure(qr_a, cr_c) - q_circuit_2.measure(qr_b, cr_d) - self.assertEqual(q_circuit, q_circuit_2) - - def test_loading_all_qelib1_gates(self): - """Test setting up a circuit with all gates defined in qiskit/qasm/libs/qelib1.inc.""" - from qiskit.circuit.library import U1Gate, U2Gate, U3Gate, CU1Gate, CU3Gate, UGate - - all_gates_qasm = os.path.join(self.qasm_dir, "all_gates.qasm") - qasm_circuit = from_qasm_file(all_gates_qasm) - - ref_circuit = QuantumCircuit(3, 3) - - # abstract gates (legacy) - ref_circuit.append(UGate(0.2, 0.1, 0.6), [0]) - ref_circuit.cx(0, 1) - # the hardware primitives - ref_circuit.append(U3Gate(0.2, 0.1, 0.6), [0]) - ref_circuit.append(U2Gate(0.1, 0.6), [0]) - ref_circuit.append(U1Gate(0.6), [0]) - ref_circuit.id(0) - ref_circuit.cx(0, 1) - # the standard single qubit gates - ref_circuit.u(0.2, 0.1, 0.6, 0) - ref_circuit.p(0.6, 0) - ref_circuit.x(0) - ref_circuit.y(0) - ref_circuit.z(0) - ref_circuit.h(0) - ref_circuit.s(0) - ref_circuit.t(0) - ref_circuit.sdg(0) - ref_circuit.tdg(0) - ref_circuit.sx(0) - ref_circuit.sxdg(0) - # the standard rotations - ref_circuit.rx(0.1, 0) - ref_circuit.ry(0.1, 0) - ref_circuit.rz(0.1, 0) - # the barrier - ref_circuit.barrier() - # the standard user-defined gates - ref_circuit.swap(0, 1) - ref_circuit.cswap(0, 1, 2) - ref_circuit.cy(0, 1) - ref_circuit.cz(0, 1) - ref_circuit.ch(0, 1) - ref_circuit.csx(0, 1) - ref_circuit.append(CU1Gate(0.6), [0, 1]) - ref_circuit.append(CU3Gate(0.2, 0.1, 0.6), [0, 1]) - ref_circuit.cp(0.6, 0, 1) - ref_circuit.cu(0.2, 0.1, 0.6, 0, 0, 1) - ref_circuit.ccx(0, 1, 2) - ref_circuit.crx(0.6, 0, 1) - ref_circuit.cry(0.6, 0, 1) - ref_circuit.crz(0.6, 0, 1) - ref_circuit.rxx(0.2, 0, 1) - ref_circuit.rzz(0.2, 0, 1) - ref_circuit.measure([0, 1, 2], [0, 1, 2]) - - self.assertEqual(qasm_circuit, ref_circuit) - - def test_fail_qasm_file(self): - """ - Test fail_qasm_file. - - If all is correct we should get a QiskitError - """ - self.assertRaises(QiskitError, from_qasm_file, "") - - def test_qasm_text(self): - """ - Test qasm_text and get_circuit. - - If all is correct we should get the qasm file loaded from the string - """ - qasm_string = "// A simple 8 qubit example\nOPENQASM 2.0;\n" - qasm_string += 'include "qelib1.inc";\nqreg a[4];\n' - qasm_string += "qreg b[4];\ncreg c[4];\ncreg d[4];\nh a;\ncx a, b;\n" - qasm_string += "barrier a;\nbarrier b;\nmeasure a[0]->c[0];\n" - qasm_string += "measure a[1]->c[1];\nmeasure a[2]->c[2];\n" - qasm_string += "measure a[3]->c[3];\nmeasure b[0]->d[0];\n" - qasm_string += "measure b[1]->d[1];\nmeasure b[2]->d[2];\n" - qasm_string += "measure b[3]->d[3];" - q_circuit = from_qasm_str(qasm_string) - - qr_a = QuantumRegister(4, "a") - qr_b = QuantumRegister(4, "b") - cr_c = ClassicalRegister(4, "c") - cr_d = ClassicalRegister(4, "d") - ref = QuantumCircuit(qr_a, qr_b, cr_c, cr_d) - ref.h(qr_a[3]) - ref.cx(qr_a[3], qr_b[3]) - ref.h(qr_a[2]) - ref.cx(qr_a[2], qr_b[2]) - ref.h(qr_a[1]) - ref.cx(qr_a[1], qr_b[1]) - ref.h(qr_a[0]) - ref.cx(qr_a[0], qr_b[0]) - ref.barrier(qr_b) - ref.measure(qr_b, cr_d) - ref.barrier(qr_a) - ref.measure(qr_a, cr_c) - - self.assertEqual(len(q_circuit.cregs), 2) - self.assertEqual(len(q_circuit.qregs), 2) - self.assertEqual(q_circuit, ref) - - def test_qasm_text_conditional(self): - """ - Test qasm_text and get_circuit when conditionals are present. - """ - qasm_string = ( - "\n".join( - [ - "OPENQASM 2.0;", - 'include "qelib1.inc";', - "qreg q[1];", - "creg c0[4];", - "creg c1[4];", - "x q[0];", - "if(c1==4) x q[0];", - ] - ) - + "\n" - ) - q_circuit = from_qasm_str(qasm_string) - - qr = QuantumRegister(1, "q") - cr0 = ClassicalRegister(4, "c0") - cr1 = ClassicalRegister(4, "c1") - ref = QuantumCircuit(qr, cr0, cr1) - ref.x(qr[0]) - ref.x(qr[0]).c_if(cr1, 4) - - self.assertEqual(len(q_circuit.cregs), 2) - self.assertEqual(len(q_circuit.qregs), 1) - self.assertEqual(q_circuit, ref) - - def test_opaque_gate(self): - """ - Test parse an opaque gate - - See https://github.com/Qiskit/qiskit-terra/issues/1566. - """ - - qasm_string = ( - "\n".join( - [ - "OPENQASM 2.0;", - 'include "qelib1.inc";', - "opaque my_gate(theta,phi,lambda) a,b;", - "qreg q[3];", - "my_gate(1,2,3) q[1],q[2];", - ] - ) - + "\n" - ) - circuit = from_qasm_str(qasm_string) - - qr = QuantumRegister(3, "q") - expected = QuantumCircuit(qr) - expected.append(Gate(name="my_gate", num_qubits=2, params=[1, 2, 3]), [qr[1], qr[2]]) - - self.assertEqual(circuit, expected) - - def test_qasm_example_file(self): - """Loads qasm/example.qasm.""" - qasm_filename = os.path.join(self.qasm_dir, "example.qasm") - expected_circuit = from_qasm_str( - "\n".join( - [ - "OPENQASM 2.0;", - 'include "qelib1.inc";', - "qreg q[3];", - "qreg r[3];", - "creg c[3];", - "creg d[3];", - "h q[2];", - "cx q[2],r[2];", - "measure r[2] -> d[2];", - "h q[1];", - "cx q[1],r[1];", - "measure r[1] -> d[1];", - "h q[0];", - "cx q[0],r[0];", - "measure r[0] -> d[0];", - "barrier q[0],q[1],q[2];", - "measure q[2] -> c[2];", - "measure q[1] -> c[1];", - "measure q[0] -> c[0];", - ] - ) - + "\n" - ) - - q_circuit = from_qasm_file(qasm_filename) - - self.assertEqual(q_circuit, expected_circuit) - self.assertEqual(len(q_circuit.cregs), 2) - self.assertEqual(len(q_circuit.qregs), 2) - - def test_qasm_qas_string_order(self): - """Test that gates are returned in qasm in ascending order.""" - expected_qasm = ( - "\n".join( - [ - "OPENQASM 2.0;", - 'include "qelib1.inc";', - "qreg q[3];", - "h q[0];", - "h q[1];", - "h q[2];", - ] - ) - + "\n" - ) - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - qreg q[3]; - h q;""" - q_circuit = from_qasm_str(qasm_string) - - self.assertEqual(q_circuit.qasm(), expected_qasm) - - def test_from_qasm_str_custom_gate1(self): - """Test load custom gates (simple case)""" - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate rinv q {sdg q; h q; sdg q; h q; } - qreg qr[1]; - rinv qr[0];""" - circuit = from_qasm_str(qasm_string) - - rinv_q = QuantumRegister(1, name="q") - rinv_gate = QuantumCircuit(rinv_q, name="rinv") - rinv_gate.sdg(rinv_q) - rinv_gate.h(rinv_q) - rinv_gate.sdg(rinv_q) - rinv_gate.h(rinv_q) - rinv = rinv_gate.to_instruction() - qr = QuantumRegister(1, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.append(rinv, [qr[0]]) - - self.assertEqualUnroll(["sdg", "h"], circuit, expected) - - def test_from_qasm_str_custom_gate2(self): - """Test load custom gates (no so simple case, different bit order) - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate swap2 a,b { - cx a,b; - cx b,a; // different bit order - cx a,b; - } - qreg qr[3]; - swap2 qr[0], qr[1]; - swap2 qr[1], qr[2];""" - circuit = from_qasm_str(qasm_string) - - ab_args = QuantumRegister(2, name="ab") - swap_gate = QuantumCircuit(ab_args, name="swap2") - swap_gate.cx(ab_args[0], ab_args[1]) - swap_gate.cx(ab_args[1], ab_args[0]) - swap_gate.cx(ab_args[0], ab_args[1]) - swap = swap_gate.to_instruction() - - qr = QuantumRegister(3, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.append(swap, [qr[0], qr[1]]) - expected.append(swap, [qr[1], qr[2]]) - - self.assertEqualUnroll(["cx"], expected, circuit) - - def test_from_qasm_str_custom_gate3(self): - """Test load custom gates (no so simple case, different bit count) - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate cswap2 a,b,c - { - cx c,b; // different bit count - ccx a,b,c; //previously defined gate - cx c,b; - } - qreg qr[3]; - cswap2 qr[1], qr[0], qr[2];""" - circuit = from_qasm_str(qasm_string) - - abc_args = QuantumRegister(3, name="abc") - cswap_gate = QuantumCircuit(abc_args, name="cswap2") - cswap_gate.cx(abc_args[2], abc_args[1]) - cswap_gate.ccx(abc_args[0], abc_args[1], abc_args[2]) - cswap_gate.cx(abc_args[2], abc_args[1]) - cswap = cswap_gate.to_instruction() - - qr = QuantumRegister(3, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.append(cswap, [qr[1], qr[0], qr[2]]) - - self.assertEqualUnroll(["cx", "h", "tdg", "t"], circuit, expected) - - def test_from_qasm_str_custom_gate4(self): - """Test load custom gates (parameterized) - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate my_gate(phi,lambda) q {u(1.5707963267948966,phi,lambda) q;} - qreg qr[1]; - my_gate(pi, pi) qr[0];""" - circuit = from_qasm_str(qasm_string) - - my_gate_circuit = QuantumCircuit(1, name="my_gate") - phi = Parameter("phi") - lam = Parameter("lambda") - my_gate_circuit.u(1.5707963267948966, phi, lam, 0) - my_gate = my_gate_circuit.to_gate() - - qr = QuantumRegister(1, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.append(my_gate, [qr[0]]) - expected = expected.assign_parameters({phi: 3.141592653589793, lam: 3.141592653589793}) - - self.assertEqualUnroll("u", circuit, expected) - - def test_from_qasm_str_custom_gate5(self): - """Test load custom gates (parameterized, with biop and constant) - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate my_gate(phi,lambda) q {u(pi/2,phi,lambda) q;} // biop with pi - qreg qr[1]; - my_gate(pi, pi) qr[0];""" - circuit = from_qasm_str(qasm_string) - - my_gate_circuit = QuantumCircuit(1, name="my_gate") - phi = Parameter("phi") - lam = Parameter("lambda") - my_gate_circuit.u(1.5707963267948966, phi, lam, 0) - my_gate = my_gate_circuit.to_gate() - - qr = QuantumRegister(1, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.append(my_gate, [qr[0]]) - expected = expected.assign_parameters({phi: 3.141592653589793, lam: 3.141592653589793}) - - self.assertEqualUnroll("u", circuit, expected) - - def test_from_qasm_str_custom_gate6(self): - """Test load custom gates (parameters used in expressions) - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-591668924 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate my_gate(phi,lambda) q - {rx(phi+pi) q; ry(lambda/2) q;} // parameters used in expressions - qreg qr[1]; - my_gate(pi, pi) qr[0];""" - circuit = from_qasm_str(qasm_string) - - my_gate_circuit = QuantumCircuit(1, name="my_gate") - phi = Parameter("phi") - lam = Parameter("lambda") - my_gate_circuit.rx(phi + 3.141592653589793, 0) - my_gate_circuit.ry(lam / 2, 0) - my_gate = my_gate_circuit.to_gate() - - qr = QuantumRegister(1, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.append(my_gate, [qr[0]]) - expected = expected.assign_parameters({phi: 3.141592653589793, lam: 3.141592653589793}) - - self.assertEqualUnroll(["rx", "ry"], circuit, expected) - - def test_from_qasm_str_custom_gate7(self): - """Test load custom gates (build in functions) - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-592208951 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate my_gate(phi,lambda) q - {u(asin(cos(phi)/2), phi+pi, lambda/2) q;} // build func - qreg qr[1]; - my_gate(pi, pi) qr[0];""" - circuit = from_qasm_str(qasm_string) - - qr = QuantumRegister(1, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.u(-0.5235987755982988, 6.283185307179586, 1.5707963267948966, qr[0]) - self.assertEqualUnroll("u", circuit, expected) - - def test_from_qasm_str_nested_custom_gate(self): - """Test chain of custom gates - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-592261942 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate my_other_gate(phi,lambda) q - {u(asin(cos(phi)/2), phi+pi, lambda/2) q;} - gate my_gate(phi) r - {my_other_gate(phi, phi+pi) r;} - qreg qr[1]; - my_gate(pi) qr[0];""" - circuit = from_qasm_str(qasm_string) - - qr = QuantumRegister(1, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.u(-0.5235987755982988, 6.283185307179586, 3.141592653589793, qr[0]) - self.assertEqualUnroll("u", circuit, expected) - - def test_from_qasm_str_delay(self): - """Test delay instruction/opaque-gate - See: https://github.com/Qiskit/qiskit-terra/issues/6510 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - - opaque delay(time) q; - - qreg q[1]; - delay(172) q[0];""" - circuit = from_qasm_str(qasm_string) - - qr = QuantumRegister(1, name="q") - expected = QuantumCircuit(qr, name="circuit") - expected.delay(172, qr[0]) - self.assertEqualUnroll("u", circuit, expected) - - def assertEqualUnroll(self, basis, circuit, expected): - """Compares the dags after unrolling to basis""" - circuit_dag = circuit_to_dag(circuit) - expected_dag = circuit_to_dag(expected) - with self.assertWarns(DeprecationWarning): - circuit_result = Unroller(basis).run(circuit_dag) - expected_result = Unroller(basis).run(expected_dag) - - self.assertEqual(circuit_result, expected_result) diff --git a/test/python/test_qasm_parser.py b/test/python/test_qasm_parser.py deleted file mode 100644 index 49cdc2f12673..000000000000 --- a/test/python/test_qasm_parser.py +++ /dev/null @@ -1,126 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test for the QASM parser""" - -import os -import unittest -import ply -import ddt - -from qiskit.qasm import Qasm, QasmError -from qiskit.qasm.node.node import Node -from qiskit.test import QiskitTestCase - - -def parse(file_path): - """ - Simple helper - - file_path: Path to the OpenQASM file - - prec: Precision for the returned string - """ - qasm = Qasm(file_path) - return qasm.parse().qasm() - - -@ddt.ddt -class TestParser(QiskitTestCase): - """QasmParser""" - - def setUp(self): - super().setUp() - self.qasm_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "qasm") - self.qasm_file_path = os.path.join(self.qasm_dir, "example.qasm") - self.qasm_file_path_fail = os.path.join(self.qasm_dir, "example_fail.qasm") - self.qasm_file_path_if = os.path.join(self.qasm_dir, "example_if.qasm") - self.qasm_file_path_version_fail = os.path.join(self.qasm_dir, "example_version_fail.qasm") - self.qasm_file_path_version_2 = os.path.join(self.qasm_dir, "example_version_2.qasm") - self.qasm_file_path_minor_ver_fail = os.path.join( - self.qasm_dir, "example_minor_version_fail.qasm" - ) - - def test_parser(self): - """should return a correct response for a valid circuit.""" - - res = parse(self.qasm_file_path) - self.log.info(res) - # TODO: For now only some basic checks. - starts_expected = "OPENQASM 2.0;\ngate " - ends_expected = "\n".join( - [ - "}", - "qreg q[3];", - "qreg r[3];", - "h q;", - "cx q,r;", - "creg c[3];", - "creg d[3];", - "barrier q;", - "measure q -> c;", - "measure r -> d;", - "", - ] - ) - - self.assertEqual(res[: len(starts_expected)], starts_expected) - self.assertEqual(res[-len(ends_expected) :], ends_expected) - - def test_parser_fail(self): - """should fail a for a not valid circuit.""" - - self.assertRaisesRegex( - QasmError, "Perhaps there is a missing", parse, file_path=self.qasm_file_path_fail - ) - - @ddt.data("example_version_fail.qasm", "example_minor_version_fail.qasm") - def test_parser_version_fail(self, filename): - """Ensure versions other than 2.0 or 2 fail.""" - filename = os.path.join(self.qasm_dir, filename) - with self.assertRaisesRegex( - QasmError, r"Invalid version: '.+'\. This module supports OpenQASM 2\.0 only\." - ): - parse(filename) - - def test_parser_version_2(self): - """should succeed for OPENQASM version 2. Parser should automatically add minor verison.""" - res = parse(self.qasm_file_path_version_2) - version_start = "OPENQASM 2.0;" - self.assertEqual(res[: len(version_start)], version_start) - - def test_all_valid_nodes(self): - """Test that the tree contains only Node subclasses.""" - - def inspect(node): - """Inspect node children.""" - for child in node.children: - self.assertTrue(isinstance(child, Node)) - inspect(child) - - # Test the canonical example file. - qasm = Qasm(self.qasm_file_path) - res = qasm.parse() - inspect(res) - - # Test a file containing if instructions. - qasm_if = Qasm(self.qasm_file_path_if) - res_if = qasm_if.parse() - inspect(res_if) - - def test_generate_tokens(self): - """Test whether we get only valid tokens.""" - qasm = Qasm(self.qasm_file_path) - for token in qasm.generate_tokens(): - self.assertTrue(isinstance(token, ply.lex.LexToken)) - - -if __name__ == "__main__": - unittest.main() From 35567401c029385a5f02575f3f5a3d6ab14b6ad5 Mon Sep 17 00:00:00 2001 From: sven-oxionics Date: Wed, 29 Nov 2023 14:19:16 +0000 Subject: [PATCH 7/7] Decomposition of single R gate in RR basis should be one R gate, not two (#11304) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Decomposition of single R gate in RR basis should be one R gate, not two Currently, a single R gate may come out of RR-decomposition as two R gates, which is obviously not ideal. For example, `RGate(0.1, 0.2)` is currently decomposed (in RR basis) as ``` ┌────────────────┐┌──────────┐ 0: ┤ R(-3.0416,0.2) ├┤ R(π,0.2) ├ └────────────────┘└──────────┘ ``` Two `R(𝜗, 𝜑)` gates with the same 𝜑 parameter can be combined into one by simply adding up the 𝜗 values (giving us `R(0.1, 0.2)`, unsurprisingly). In terms of `U(𝜗, 𝜑, 𝜆)`, it is the case when `𝜑 = -𝜆` that the two R-gates we construct have the same second parameter and therefore should be expressed as a single R gate. For example, `U3Gate(0.1, 0.2, -0.2)` currently produces this RR-decomposition: ``` ┌───────────────────┐┌─────────────┐ 0: ┤ R(-3.0416,1.7708) ├┤ R(π,1.7708) ├ └───────────────────┘└─────────────┘ ``` which also unnecessarily consists of two R gates instead of just one. This commit adds the two examples above as unit tests, ensuring they RR-decompose two just one R gate, as well as the code changes to make these two new tests pass, along with all existing tests, of course. The condition for this special case is that the 𝜑 parameters of the two R-gates we would emit are the same (thus `mod_2pi(PI / 2. - lam)=mod_2pi(0.5 * (phi - lam + PI)`, simplified as `mod_2pi((phi + lam) / 2)=0`). * Add release note --- .../src/euler_one_qubit_decomposer.rs | 28 +++++++++++-------- ...omposition-synthesis-70eb88ada9305916.yaml | 8 ++++++ test/python/quantum_info/test_synthesis.py | 3 ++ 3 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/rr-decomposition-synthesis-70eb88ada9305916.yaml diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 1a37528dbc8e..8fe0eaf87b45 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -380,22 +380,26 @@ fn circuit_rr( if !simplify { atol = -1.0; } - if theta.abs() < atol && phi.abs() < atol && lam.abs() < atol { - return OneQubitGateSequence { - gates: circuit, - global_phase: phase, - }; - } - if (theta - PI).abs() > atol { + + if mod_2pi((phi + lam) / 2., atol).abs() < atol { + // This can be expressed as a single R gate + if theta.abs() > atol { + circuit.push((String::from("r"), vec![theta, mod_2pi(PI / 2. + phi, atol)])); + } + } else { + // General case: use two R gates + if (theta - PI).abs() > atol { + circuit.push(( + String::from("r"), + vec![theta - PI, mod_2pi(PI / 2. - lam, atol)], + )); + } circuit.push(( String::from("r"), - vec![theta - PI, mod_2pi(PI / 2. - lam, atol)], + vec![PI, mod_2pi(0.5 * (phi - lam + PI), atol)], )); } - circuit.push(( - String::from("r"), - vec![PI, mod_2pi(0.5 * (phi - lam + PI), atol)], - )); + OneQubitGateSequence { gates: circuit, global_phase: phase, diff --git a/releasenotes/notes/rr-decomposition-synthesis-70eb88ada9305916.yaml b/releasenotes/notes/rr-decomposition-synthesis-70eb88ada9305916.yaml new file mode 100644 index 000000000000..e4ec6a970a3c --- /dev/null +++ b/releasenotes/notes/rr-decomposition-synthesis-70eb88ada9305916.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fixed an issue in :func:`.unitary_to_gate_sequence` which caused unitary + decomposition in RR basis to emit two R gates in some cases where the + matrix can be expressed as a single R gate. Previously, in those cases you + would get two R gates with the same phi parameter. Now, they are combined + into one. diff --git a/test/python/quantum_info/test_synthesis.py b/test/python/quantum_info/test_synthesis.py index 2d134967e34f..c04337d3ffd3 100644 --- a/test/python/quantum_info/test_synthesis.py +++ b/test/python/quantum_info/test_synthesis.py @@ -30,6 +30,7 @@ from qiskit.circuit.library import ( HGate, IGate, + RGate, SdgGate, SGate, U3Gate, @@ -446,6 +447,8 @@ def test_special_RR(self): self.check_oneq_special_cases(U3Gate(-np.pi, 0.2, 0.0).to_matrix(), "RR", {"r": 1}) self.check_oneq_special_cases(U3Gate(np.pi, 0.0, 0.2).to_matrix(), "RR", {"r": 1}) self.check_oneq_special_cases(U3Gate(0.1, 0.2, 0.3).to_matrix(), "RR", {"r": 2}) + self.check_oneq_special_cases(U3Gate(0.1, 0.2, -0.2).to_matrix(), "RR", {"r": 1}) + self.check_oneq_special_cases(RGate(0.1, 0.2).to_matrix(), "RR", {"r": 1}) def test_special_U1X(self): """Special cases of U1X"""