From 32ee4dbb5a003039ffec88c8d88883af85ca6456 Mon Sep 17 00:00:00 2001 From: nick bronn Date: Sun, 18 Apr 2021 21:42:30 -0400 Subject: [PATCH 01/84] added new equivalence rzx_zz4 --- .../circuit/library/templates/rzx/__init__.py | 1 + .../circuit/library/templates/rzx/rzx_zz4.py | 58 +++++++++++++++++++ .../passes/scheduling/rzx_templates.py | 6 +- 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 qiskit/circuit/library/templates/rzx/rzx_zz4.py diff --git a/qiskit/circuit/library/templates/rzx/__init__.py b/qiskit/circuit/library/templates/rzx/__init__.py index 05895d875c4e..0dfe1235f756 100644 --- a/qiskit/circuit/library/templates/rzx/__init__.py +++ b/qiskit/circuit/library/templates/rzx/__init__.py @@ -23,3 +23,4 @@ from .rzx_zz1 import rzx_zz1 from .rzx_zz2 import rzx_zz2 from .rzx_zz3 import rzx_zz3 +from .rzx_zz4 import rzx_zz4 diff --git a/qiskit/circuit/library/templates/rzx/rzx_zz4.py b/qiskit/circuit/library/templates/rzx/rzx_zz4.py new file mode 100644 index 000000000000..24fbd8116884 --- /dev/null +++ b/qiskit/circuit/library/templates/rzx/rzx_zz4.py @@ -0,0 +1,58 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# 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. + +""" +RZX based template for a CX - singles - CX pattern +.. parsed-literal:: + + ┌───────┐ ┌───────┐ ┌────────┐ » +q_0: ──■───┤ RX(π) ├─┤ RZ(φ) ├──■───┤ RZ(-φ) ├───────────────────────» + ┌─┴─┐┌┴───────┴┐├───────┤┌─┴─┐┌┴────────┤┌─────────┐┌──────────┐» +q_1: ┤ X ├┤ RX(π/2) ├┤ RZ(θ) ├┤ X ├┤ RX(π/2) ├┤ RZ(π/2) ├┤ RX(-π/2) ├» + └───┘└─────────┘└───────┘└───┘└─────────┘└─────────┘└──────────┘» +« ┌──────────┐ ┌────────┐ +«q_0: ┤0 ├─┤ RX(-π) ├──────────── +« │ RZX(-θ) │┌┴────────┤┌──────────┐ +«q_1: ┤1 ├┤ RX(π/2) ├┤ RZ(-π/2) ├ +« └──────────┘└─────────┘└──────────┘ +""" + +import numpy as np +from qiskit.circuit import Parameter, QuantumCircuit + + +def rzx_zz4(theta: float = None, phi: float = None): + """Template for a CX - singles - CX pattern.""" + if theta is None: + theta = Parameter('ϴ') + if phi is None: + phi = Parameter('φ') + + qc = QuantumCircuit(2) + + qc.cx(0,1) + qc.rx(np.pi, 0) + qc.rz(phi, 0) + qc.rx(np.pi/2, 1) + qc.rz(theta, 1) + qc.cx(0,1) + + qc.rz(-1*phi, 0) + qc.rx(np.pi/2, 1) + qc.rz(np.pi/2, 1) + qc.rx(-np.pi/2, 1) + qc.rzx(-1*theta, 0, 1) + qc.rx(-np.pi, 0) + qc.rx(np.pi/2, 1) + qc.rz(-np.pi/2, 1) + + return qc diff --git a/qiskit/transpiler/passes/scheduling/rzx_templates.py b/qiskit/transpiler/passes/scheduling/rzx_templates.py index 0cc01025cc55..c619f68f3e2b 100644 --- a/qiskit/transpiler/passes/scheduling/rzx_templates.py +++ b/qiskit/transpiler/passes/scheduling/rzx_templates.py @@ -16,7 +16,7 @@ from typing import List -from qiskit.circuit.library.templates.rzx import rzx_zz1, rzx_zz2, rzx_zz3, rzx_yz, rzx_xz, rzx_cy +from qiskit.circuit.library.templates.rzx import rzx_zz1, rzx_zz2, rzx_zz3, rzx_zz4, rzx_yz, rzx_xz, rzx_cy def rzx_templates(template_list: List[str] = None): @@ -26,7 +26,7 @@ def rzx_templates(template_list: List[str] = None): """ if template_list is None: - template_list = ['zz1', 'zz2', 'zz3', 'yz', 'xz', 'cy'] + template_list = ['zz1', 'zz2', 'zz3', 'zz4', 'yz', 'xz', 'cy'] templates = [] if 'zz1' in template_list: @@ -35,6 +35,8 @@ def rzx_templates(template_list: List[str] = None): templates.append(rzx_zz2()) if 'zz3' in template_list: templates.append(rzx_zz3()) + if 'zz4' in template_list: + templates.append(rzx_zz4()) if 'yz' in template_list: templates.append(rzx_yz()) if 'xz' in template_list: From 8cfc6d94018d5b4d2f39b06c8f086b48902418ec Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Tue, 27 Apr 2021 19:48:42 -0400 Subject: [PATCH 02/84] added another zz-ish template (looks for basis gates) --- qiskit/circuit/library/templates/rzx/rzx_zz4.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/qiskit/circuit/library/templates/rzx/rzx_zz4.py b/qiskit/circuit/library/templates/rzx/rzx_zz4.py index 24fbd8116884..ee7d4fa138a7 100644 --- a/qiskit/circuit/library/templates/rzx/rzx_zz4.py +++ b/qiskit/circuit/library/templates/rzx/rzx_zz4.py @@ -14,9 +14,9 @@ RZX based template for a CX - singles - CX pattern .. parsed-literal:: - ┌───────┐ ┌───────┐ ┌────────┐ » -q_0: ──■───┤ RX(π) ├─┤ RZ(φ) ├──■───┤ RZ(-φ) ├───────────────────────» - ┌─┴─┐┌┴───────┴┐├───────┤┌─┴─┐┌┴────────┤┌─────────┐┌──────────┐» + ┌───────┐ » +q_0: ──■───┤ RX(π) ├────────────■────────────────────────────────────» + ┌─┴─┐┌┴───────┴┐┌───────┐┌─┴─┐┌─────────┐┌─────────┐┌──────────┐» q_1: ┤ X ├┤ RX(π/2) ├┤ RZ(θ) ├┤ X ├┤ RX(π/2) ├┤ RZ(π/2) ├┤ RX(-π/2) ├» └───┘└─────────┘└───────┘└───┘└─────────┘└─────────┘└──────────┘» « ┌──────────┐ ┌────────┐ @@ -30,23 +30,19 @@ from qiskit.circuit import Parameter, QuantumCircuit -def rzx_zz4(theta: float = None, phi: float = None): +def rzx_zz4(theta: float = None): """Template for a CX - singles - CX pattern.""" if theta is None: theta = Parameter('ϴ') - if phi is None: - phi = Parameter('φ') qc = QuantumCircuit(2) qc.cx(0,1) qc.rx(np.pi, 0) - qc.rz(phi, 0) qc.rx(np.pi/2, 1) qc.rz(theta, 1) qc.cx(0,1) - qc.rz(-1*phi, 0) qc.rx(np.pi/2, 1) qc.rz(np.pi/2, 1) qc.rx(-np.pi/2, 1) From ad85838b623b3da2415df810fe895a52da3c735b Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 30 Jun 2021 21:13:28 -0400 Subject: [PATCH 03/84] looking at adding additions to rzx templates --- qiskit/transpiler/passes/scheduling/rzx_templates.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/qiskit/transpiler/passes/scheduling/rzx_templates.py b/qiskit/transpiler/passes/scheduling/rzx_templates.py index e8893ab85e47..b75329688aae 100644 --- a/qiskit/transpiler/passes/scheduling/rzx_templates.py +++ b/qiskit/transpiler/passes/scheduling/rzx_templates.py @@ -26,11 +26,7 @@ def rzx_templates(template_list: List[str] = None): """ if template_list is None: -<<<<<<< HEAD - template_list = ['zz1', 'zz2', 'zz3', 'zz4', 'yz', 'xz', 'cy'] -======= template_list = ["zz1", "zz2", "zz3", "yz", "xz", "cy"] ->>>>>>> main templates = [] if "zz1" in template_list: @@ -39,13 +35,9 @@ def rzx_templates(template_list: List[str] = None): templates.append(rzx_zz2()) if "zz3" in template_list: templates.append(rzx_zz3()) -<<<<<<< HEAD if 'zz4' in template_list: templates.append(rzx_zz4()) if 'yz' in template_list: -======= - if "yz" in template_list: ->>>>>>> main templates.append(rzx_yz()) if "xz" in template_list: templates.append(rzx_xz()) From 7098e4fc85d609e8140c43c5f6a0b25b468d4310 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Fri, 2 Jul 2021 15:48:54 -0400 Subject: [PATCH 04/84] fixes parsing of Parameter name for sympy during template optimization --- .../template_matching/template_substitution.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 3390abd6c0c8..5ee7a91b25b4 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -503,7 +503,12 @@ def _attempt_bind(self, template_sublist, circuit_sublist): equations, symbols, sol, fake_bind = [], set(), {}, {} for t_idx, params in enumerate(template_params): if isinstance(params, ParameterExpression): - equations.append(sym.Eq(parse_expr(str(params)), circuit_params[t_idx])) + cpn = circuit_params[t_idx].name + if len(cpn) == 1: + param_symbol = sym.Eq(parse_expr(cpn)) + else: + param_symbol = sym.symbols(str(cpn.split('$')[1][1:])) + equations.append(sym.Eq(parse_expr(str(params)), param_symbol)) for param in params.parameters: symbols.add(param) From c7605d7884e93043f1a00f5a2a780977e84e7c9d Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Tue, 6 Jul 2021 18:14:53 -0400 Subject: [PATCH 05/84] generalized fix for multiple parameters in an expression and constant parameters, removed some excess files from another branch --- .../circuit/library/templates/rzx/__init__.py | 1 - .../circuit/library/templates/rzx/rzx_zz4.py | 54 ------------------- .../template_substitution.py | 18 ++++--- .../passes/scheduling/rzx_templates.py | 4 +- 4 files changed, 13 insertions(+), 64 deletions(-) delete mode 100644 qiskit/circuit/library/templates/rzx/rzx_zz4.py diff --git a/qiskit/circuit/library/templates/rzx/__init__.py b/qiskit/circuit/library/templates/rzx/__init__.py index 0dfe1235f756..05895d875c4e 100644 --- a/qiskit/circuit/library/templates/rzx/__init__.py +++ b/qiskit/circuit/library/templates/rzx/__init__.py @@ -23,4 +23,3 @@ from .rzx_zz1 import rzx_zz1 from .rzx_zz2 import rzx_zz2 from .rzx_zz3 import rzx_zz3 -from .rzx_zz4 import rzx_zz4 diff --git a/qiskit/circuit/library/templates/rzx/rzx_zz4.py b/qiskit/circuit/library/templates/rzx/rzx_zz4.py deleted file mode 100644 index ee7d4fa138a7..000000000000 --- a/qiskit/circuit/library/templates/rzx/rzx_zz4.py +++ /dev/null @@ -1,54 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. - -""" -RZX based template for a CX - singles - CX pattern -.. parsed-literal:: - - ┌───────┐ » -q_0: ──■───┤ RX(π) ├────────────■────────────────────────────────────» - ┌─┴─┐┌┴───────┴┐┌───────┐┌─┴─┐┌─────────┐┌─────────┐┌──────────┐» -q_1: ┤ X ├┤ RX(π/2) ├┤ RZ(θ) ├┤ X ├┤ RX(π/2) ├┤ RZ(π/2) ├┤ RX(-π/2) ├» - └───┘└─────────┘└───────┘└───┘└─────────┘└─────────┘└──────────┘» -« ┌──────────┐ ┌────────┐ -«q_0: ┤0 ├─┤ RX(-π) ├──────────── -« │ RZX(-θ) │┌┴────────┤┌──────────┐ -«q_1: ┤1 ├┤ RX(π/2) ├┤ RZ(-π/2) ├ -« └──────────┘└─────────┘└──────────┘ -""" - -import numpy as np -from qiskit.circuit import Parameter, QuantumCircuit - - -def rzx_zz4(theta: float = None): - """Template for a CX - singles - CX pattern.""" - if theta is None: - theta = Parameter('ϴ') - - qc = QuantumCircuit(2) - - qc.cx(0,1) - qc.rx(np.pi, 0) - qc.rx(np.pi/2, 1) - qc.rz(theta, 1) - qc.cx(0,1) - - qc.rx(np.pi/2, 1) - qc.rz(np.pi/2, 1) - qc.rx(-np.pi/2, 1) - qc.rzx(-1*theta, 0, 1) - qc.rx(-np.pi, 0) - qc.rx(np.pi/2, 1) - qc.rz(-np.pi/2, 1) - - return qc diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 5ee7a91b25b4..9c840feb0e67 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -503,12 +503,18 @@ def _attempt_bind(self, template_sublist, circuit_sublist): equations, symbols, sol, fake_bind = [], set(), {}, {} for t_idx, params in enumerate(template_params): if isinstance(params, ParameterExpression): - cpn = circuit_params[t_idx].name - if len(cpn) == 1: - param_symbol = sym.Eq(parse_expr(cpn)) - else: - param_symbol = sym.symbols(str(cpn.split('$')[1][1:])) - equations.append(sym.Eq(parse_expr(str(params)), param_symbol)) + #import pdb; pdb.set_trace() + + param_symbols = set() + if isinstance(circuit_params[t_idx], ParameterExpression): + for param in circuit_params[t_idx].parameters: + cpn = param.name + if len(cpn) == 1: + param_symbols.add(sym.Eq(parse_expr(cpn))) + else: + param_symbols.add(sym.symbols(str(cpn.split('$')[1][1:]))) + + equations.append(sym.Eq(parse_expr(str(params)), param_symbols)) for param in params.parameters: symbols.add(param) diff --git a/qiskit/transpiler/passes/scheduling/rzx_templates.py b/qiskit/transpiler/passes/scheduling/rzx_templates.py index b75329688aae..003617c6004f 100644 --- a/qiskit/transpiler/passes/scheduling/rzx_templates.py +++ b/qiskit/transpiler/passes/scheduling/rzx_templates.py @@ -16,7 +16,7 @@ from typing import List -from qiskit.circuit.library.templates.rzx import rzx_zz1, rzx_zz2, rzx_zz3, rzx_zz4, rzx_yz, rzx_xz, rzx_cy +from qiskit.circuit.library.templates.rzx import rzx_zz1, rzx_zz2, rzx_zz3, rzx_yz, rzx_xz, rzx_cy def rzx_templates(template_list: List[str] = None): @@ -35,8 +35,6 @@ def rzx_templates(template_list: List[str] = None): templates.append(rzx_zz2()) if "zz3" in template_list: templates.append(rzx_zz3()) - if 'zz4' in template_list: - templates.append(rzx_zz4()) if 'yz' in template_list: templates.append(rzx_yz()) if "xz" in template_list: From ed74ccf4bd19e87e0c6d48f87a77413a86fd3ea3 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Tue, 6 Jul 2021 18:17:01 -0400 Subject: [PATCH 06/84] one more minor fix to bring inline with qiskit-terra main --- qiskit/transpiler/passes/scheduling/rzx_templates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/scheduling/rzx_templates.py b/qiskit/transpiler/passes/scheduling/rzx_templates.py index 003617c6004f..54a095d066df 100644 --- a/qiskit/transpiler/passes/scheduling/rzx_templates.py +++ b/qiskit/transpiler/passes/scheduling/rzx_templates.py @@ -35,7 +35,7 @@ def rzx_templates(template_list: List[str] = None): templates.append(rzx_zz2()) if "zz3" in template_list: templates.append(rzx_zz3()) - if 'yz' in template_list: + if "yz" in template_list: templates.append(rzx_yz()) if "xz" in template_list: templates.append(rzx_xz()) From e308b84285d5347cc6493154cd9f67d6f86649e9 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Thu, 8 Jul 2021 14:37:47 -0400 Subject: [PATCH 07/84] trying to fix parameter parsing for template optimization --- .../optimization/template_matching/template_substitution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 9c840feb0e67..d8670a99fc4a 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -512,7 +512,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): if len(cpn) == 1: param_symbols.add(sym.Eq(parse_expr(cpn))) else: - param_symbols.add(sym.symbols(str(cpn.split('$')[1][1:]))) + param_symbols.add(sym.symbols(str(cpn.split('$')[1].split('\\')[-1]))) equations.append(sym.Eq(parse_expr(str(params)), param_symbols)) for param in params.parameters: From 8fc1fa623f7c622830dc586ac4638430c2f1edf5 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Fri, 9 Jul 2021 17:43:29 -0400 Subject: [PATCH 08/84] made sure floats were added correctly to (template) parameters --- .../optimization/template_matching/template_substitution.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index d8670a99fc4a..6295db52568d 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -513,8 +513,9 @@ def _attempt_bind(self, template_sublist, circuit_sublist): param_symbols.add(sym.Eq(parse_expr(cpn))) else: param_symbols.add(sym.symbols(str(cpn.split('$')[1].split('\\')[-1]))) + else: + equations.append(sym.Eq(parse_expr(str(params)), circuit_params[t_idx])) - equations.append(sym.Eq(parse_expr(str(params)), param_symbols)) for param in params.parameters: symbols.add(param) From 78d33390a563fc74bb8645550bf3d0fdae75c904 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Fri, 9 Jul 2021 18:38:20 -0400 Subject: [PATCH 09/84] got template matching to work with floats, sympy still not understanding Parameters either in equations or symbols set in template_substitution.py --- .../template_substitution.py | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 6295db52568d..cc53c0f5c408 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -503,16 +503,22 @@ def _attempt_bind(self, template_sublist, circuit_sublist): equations, symbols, sol, fake_bind = [], set(), {}, {} for t_idx, params in enumerate(template_params): if isinstance(params, ParameterExpression): - #import pdb; pdb.set_trace() + import pdb; pdb.set_trace() - param_symbols = set() if isinstance(circuit_params[t_idx], ParameterExpression): - for param in circuit_params[t_idx].parameters: - cpn = param.name - if len(cpn) == 1: - param_symbols.add(sym.Eq(parse_expr(cpn))) - else: - param_symbols.add(sym.symbols(str(cpn.split('$')[1].split('\\')[-1]))) + if len(circuit_params[t_idx].parameters): + for param in circuit_params[t_idx].parameters: + cpn = param.name + if len(cpn) == 1: + psym = sym.Eq(parse_expr(cpn)) + else: + psym = sym.symbols(str(cpn.split('$')[1].split('\\')[-1])) + + symbols.add(param) + equations.append(sym.Eq(parse_expr(str(params)), psym)) + else: + equations.append(sym.Eq(parse_expr(str(params)), circuit_params[t_idx])) + else: equations.append(sym.Eq(parse_expr(str(params)), circuit_params[t_idx])) From 732e887b20442e8808a90cf9b01408cf95799aa2 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Mon, 12 Jul 2021 12:36:42 -0400 Subject: [PATCH 10/84] further modifications to accept circuit parameters for template optimization --- .../template_substitution.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index cc53c0f5c408..5218f70e5843 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -503,16 +503,14 @@ def _attempt_bind(self, template_sublist, circuit_sublist): equations, symbols, sol, fake_bind = [], set(), {}, {} for t_idx, params in enumerate(template_params): if isinstance(params, ParameterExpression): - import pdb; pdb.set_trace() - if isinstance(circuit_params[t_idx], ParameterExpression): if len(circuit_params[t_idx].parameters): for param in circuit_params[t_idx].parameters: cpn = param.name - if len(cpn) == 1: - psym = sym.Eq(parse_expr(cpn)) - else: + if '$' in cpn: psym = sym.symbols(str(cpn.split('$')[1].split('\\')[-1])) + else: + psym = sym.Eq(parse_expr(cpn)) symbols.add(param) equations.append(sym.Eq(parse_expr(str(params)), psym)) @@ -534,13 +532,19 @@ def _attempt_bind(self, template_sublist, circuit_sublist): try: sol[str(key)] = float(sym_sol[key]) except TypeError: - return None + sol[str(key)] = sym_sol[key] + #return None if not sol: return None - for param in symbols: - fake_bind[param] = sol[str(param)] + import pdb; pdb.set_trace() + #for param in symbols: + for param in sol: + if '$' in str(param): + fake_bind[param] = sol[str(param).split('$')[1].split('\\')[-1]] + else: + fake_bind[param] = sol[str(param)] for node in template_dag_dep.get_nodes(): bound_params = [] From 0c63ca2895613a938f8f15c32ca1d6dc02981f85 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 21 Jul 2021 17:20:42 -0400 Subject: [PATCH 11/84] debugging the binding of template parameters --- .../template_substitution.py | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 5218f70e5843..e41872d1fc95 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -500,34 +500,35 @@ def _attempt_bind(self, template_sublist, circuit_sublist): template_params += template_dag_dep.get_node(t_idx).op.params # Create the fake binding dict and check - equations, symbols, sol, fake_bind = [], set(), {}, {} - for t_idx, params in enumerate(template_params): - if isinstance(params, ParameterExpression): + equations, circ_symbols, temp_symbols, sol, fake_bind = [], [], set(), {}, {} + for t_idx, temp_params in enumerate(template_params): + if isinstance(temp_params, ParameterExpression): if isinstance(circuit_params[t_idx], ParameterExpression): if len(circuit_params[t_idx].parameters): - for param in circuit_params[t_idx].parameters: - cpn = param.name + for circ_param in circuit_params[t_idx].parameters: + cpn = circ_param.name if '$' in cpn: psym = sym.symbols(str(cpn.split('$')[1].split('\\')[-1])) else: psym = sym.Eq(parse_expr(cpn)) - symbols.add(param) - equations.append(sym.Eq(parse_expr(str(params)), psym)) + #circ_symbols.add(circ_param) + #circ_symbols.append(psym) + equations.append(sym.Eq(parse_expr(str(temp_params)), psym)) else: - equations.append(sym.Eq(parse_expr(str(params)), circuit_params[t_idx])) + equations.append(sym.Eq(parse_expr(str(temp_params)), circuit_params[t_idx])) else: - equations.append(sym.Eq(parse_expr(str(params)), circuit_params[t_idx])) + equations.append(sym.Eq(parse_expr(str(temp_params)), circuit_params[t_idx])) - for param in params.parameters: - symbols.add(param) + for param in temp_params.parameters: + temp_symbols.add(param) - if not symbols: + if not temp_symbols: return template_dag_dep # Check compatibility by solving the resulting equation - sym_sol = sym.solve(equations) + sym_sol = sym.solve(equations, temp_symbols) for key in sym_sol: try: sol[str(key)] = float(sym_sol[key]) @@ -538,7 +539,6 @@ def _attempt_bind(self, template_sublist, circuit_sublist): if not sol: return None - import pdb; pdb.set_trace() #for param in symbols: for param in sol: if '$' in str(param): @@ -548,11 +548,12 @@ def _attempt_bind(self, template_sublist, circuit_sublist): for node in template_dag_dep.get_nodes(): bound_params = [] - + import pdb; pdb.set_trace() for param in node.op.params: if isinstance(param, ParameterExpression): try: bound_params.append(float(param.bind(fake_bind))) + #bound_params.append(param.bind(fake_bind)) except KeyError: return None else: From 6a18dd1ca2aaab498777e4222c38deb1bb310818 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 21 Jul 2021 22:19:58 -0400 Subject: [PATCH 12/84] realized I was not treating ParameterExpressions carefully and should lean on parse_expr from sympy to cast to symbols --- .../template_substitution.py | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index e41872d1fc95..4867eec7eddb 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -500,21 +500,23 @@ def _attempt_bind(self, template_sublist, circuit_sublist): template_params += template_dag_dep.get_node(t_idx).op.params # Create the fake binding dict and check - equations, circ_symbols, temp_symbols, sol, fake_bind = [], [], set(), {}, {} + equations, circ_dict, temp_symbols, sol, fake_bind = [], {}, set(), {}, {} for t_idx, temp_params in enumerate(template_params): if isinstance(temp_params, ParameterExpression): if isinstance(circuit_params[t_idx], ParameterExpression): - if len(circuit_params[t_idx].parameters): - for circ_param in circuit_params[t_idx].parameters: - cpn = circ_param.name - if '$' in cpn: - psym = sym.symbols(str(cpn.split('$')[1].split('\\')[-1])) - else: - psym = sym.Eq(parse_expr(cpn)) - - #circ_symbols.add(circ_param) - #circ_symbols.append(psym) - equations.append(sym.Eq(parse_expr(str(temp_params)), psym)) + #if len(circuit_params[t_idx].parameters): + for circ_param in circuit_params[t_idx].parameters: + # firgure out how to parse using parse_expr + import pdb; pdb.set_trace() + cpn = circ_param.name + if '$' in cpn: + psym = sym.symbols(str(cpn.split('$')[1].split('\\')[-1])) + else: + psym = sym.Eq(parse_expr(cpn)) + + #circ_symbols.add(circ_param) + circ_dict[str(psym)] = circ_param + equations.append(sym.Eq(parse_expr(str(temp_params)), psym)) else: equations.append(sym.Eq(parse_expr(str(temp_params)), circuit_params[t_idx])) @@ -539,8 +541,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): if not sol: return None - #for param in symbols: - for param in sol: + for param in temp_symbols: if '$' in str(param): fake_bind[param] = sol[str(param).split('$')[1].split('\\')[-1]] else: @@ -552,8 +553,9 @@ def _attempt_bind(self, template_sublist, circuit_sublist): for param in node.op.params: if isinstance(param, ParameterExpression): try: - bound_params.append(float(param.bind(fake_bind))) - #bound_params.append(param.bind(fake_bind)) + #bound_params.append(float(param.bind(fake_bind))) + for key in fake_bind: + bound_params.append(param.assign(key, circ_dict[str(fake_bind[key])])) except KeyError: return None else: From 133171c41072318603fb615ba666863962d77f45 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Sun, 25 Jul 2021 17:08:36 -0400 Subject: [PATCH 13/84] converted all bindings to ParameterExpressions (via .assign()), set trace to fix template use with Parameters --- .../template_substitution.py | 38 +++++-------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 4867eec7eddb..35cb7d771d3c 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -324,6 +324,7 @@ def _substitution(self): # Fake bind any parameters in the template template = self._attempt_bind(template_sublist, circuit_sublist) + import pdb; pdb.set_trace() if template is None: continue @@ -503,28 +504,14 @@ def _attempt_bind(self, template_sublist, circuit_sublist): equations, circ_dict, temp_symbols, sol, fake_bind = [], {}, set(), {}, {} for t_idx, temp_params in enumerate(template_params): if isinstance(temp_params, ParameterExpression): - if isinstance(circuit_params[t_idx], ParameterExpression): - #if len(circuit_params[t_idx].parameters): - for circ_param in circuit_params[t_idx].parameters: - # firgure out how to parse using parse_expr - import pdb; pdb.set_trace() - cpn = circ_param.name - if '$' in cpn: - psym = sym.symbols(str(cpn.split('$')[1].split('\\')[-1])) - else: - psym = sym.Eq(parse_expr(cpn)) - - #circ_symbols.add(circ_param) - circ_dict[str(psym)] = circ_param - equations.append(sym.Eq(parse_expr(str(temp_params)), psym)) - else: - equations.append(sym.Eq(parse_expr(str(temp_params)), circuit_params[t_idx])) - - else: - equations.append(sym.Eq(parse_expr(str(temp_params)), circuit_params[t_idx])) + circ_param = ''.join(''.join(str(circuit_params[t_idx]).split('$')).split('\\')) + equations.append(sym.Eq(parse_expr(str(temp_params)), parse_expr(circ_param))) for param in temp_params.parameters: temp_symbols.add(param) + if isinstance(circuit_params[t_idx], ParameterExpression): + for param in circuit_params[t_idx].parameters: + circ_dict[param] = str(param) if not temp_symbols: return template_dag_dep @@ -533,29 +520,24 @@ def _attempt_bind(self, template_sublist, circuit_sublist): sym_sol = sym.solve(equations, temp_symbols) for key in sym_sol: try: - sol[str(key)] = float(sym_sol[key]) + sol[str(key)] = ParameterExpression(circ_dict, str(sym_sol[key])) except TypeError: - sol[str(key)] = sym_sol[key] - #return None + return None if not sol: return None for param in temp_symbols: - if '$' in str(param): - fake_bind[param] = sol[str(param).split('$')[1].split('\\')[-1]] - else: - fake_bind[param] = sol[str(param)] + fake_bind[param] = sol[str(param)] for node in template_dag_dep.get_nodes(): bound_params = [] - import pdb; pdb.set_trace() for param in node.op.params: if isinstance(param, ParameterExpression): try: #bound_params.append(float(param.bind(fake_bind))) for key in fake_bind: - bound_params.append(param.assign(key, circ_dict[str(fake_bind[key])])) + bound_params.append(param.assign(key, fake_bind[key])) except KeyError: return None else: From caee27447b8425053f91a45f043cefeee8faaf51 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Mon, 26 Jul 2021 10:48:27 -0400 Subject: [PATCH 14/84] cleaned up _attempt_bind routine --- .../template_matching/template_substitution.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 35cb7d771d3c..8662f511485c 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -324,7 +324,6 @@ def _substitution(self): # Fake bind any parameters in the template template = self._attempt_bind(template_sublist, circuit_sublist) - import pdb; pdb.set_trace() if template is None: continue @@ -504,8 +503,8 @@ def _attempt_bind(self, template_sublist, circuit_sublist): equations, circ_dict, temp_symbols, sol, fake_bind = [], {}, set(), {}, {} for t_idx, temp_params in enumerate(template_params): if isinstance(temp_params, ParameterExpression): - circ_param = ''.join(''.join(str(circuit_params[t_idx]).split('$')).split('\\')) - equations.append(sym.Eq(parse_expr(str(temp_params)), parse_expr(circ_param))) + circ_param_str = ''.join(''.join(str(circuit_params[t_idx]).split('$')).split('\\')) + equations.append(sym.Eq(parse_expr(str(temp_params)), parse_expr(circ_param_str))) for param in temp_params.parameters: temp_symbols.add(param) @@ -534,12 +533,8 @@ def _attempt_bind(self, template_sublist, circuit_sublist): bound_params = [] for param in node.op.params: if isinstance(param, ParameterExpression): - try: - #bound_params.append(float(param.bind(fake_bind))) - for key in fake_bind: - bound_params.append(param.assign(key, fake_bind[key])) - except KeyError: - return None + for key in fake_bind: + bound_params.append(param.assign(key, fake_bind[key])) else: bound_params.append(param) From bd670561b91f937329b0838f51f75a0d755581ce Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 28 Jul 2021 17:41:11 -0400 Subject: [PATCH 15/84] exploring overriding removing the matched scenarios with parameters --- .../template_matching/template_substitution.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 8662f511485c..400de9b20814 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -270,9 +270,9 @@ def _remove_impossible(self): remove_list = [] # First remove any scenarios that have parameters in the template. - for scenario in self.substitution_list: - if scenario.has_parameters(): - remove_list.append(scenario) + #for scenario in self.substitution_list: + # if scenario.has_parameters(): + # remove_list.append(scenario) # Initialize predecessors for each group of matches. for scenario in self.substitution_list: @@ -347,6 +347,8 @@ def _substitution(self): ) self.substitution_list.append(config) + import pdb; pdb.set_trace() + # Remove incompatible matches. self._remove_impossible() From 38e8bde4304d3f09115c98f6ab433b5dc7cbb8d3 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Fri, 30 Jul 2021 17:57:15 -0400 Subject: [PATCH 16/84] introduced a total hack for dealing with ParamterExpressions that contain floats for RXGates --- qiskit/circuit/library/standard_gates/rx.py | 8 +++++++- .../template_matching/template_substitution.py | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index 163484646527..7abfdecfd241 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -87,7 +87,13 @@ def inverse(self): :math:`RX(\lambda)^{\dagger} = RX(-\lambda)` """ - return RXGate(-self.params[0]) + + ### ntb: TERRIBLE HACK: + from qiskit.circuit import ParameterExpression + if isinstance(self.params[0], ParameterExpression): + return RXGate(-float(self.params[0]._symbol_expr)) + else: + return RXGate(-self.params[0]) def __array__(self, dtype=None): """Return a numpy.array for the RX gate.""" diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 400de9b20814..26eab0871466 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -347,8 +347,6 @@ def _substitution(self): ) self.substitution_list.append(config) - import pdb; pdb.set_trace() - # Remove incompatible matches. self._remove_impossible() @@ -435,6 +433,7 @@ def run_dag_opt(self): node = group.template_dag_dep.get_node(index) inst = node.op.copy() + #import pdb; pdb.set_trace() dag_dep_opt.add_op_node(inst.inverse(), qargs, cargs) # Add the unmatched gates. From a99726a7fcb4f3955c7fefd88c430c612ab332ff Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Mon, 9 Aug 2021 21:42:47 -0400 Subject: [PATCH 17/84] (non-latex) parameters now currently working in template optimization transpilig step followed by parameter binding --- qiskit/circuit/library/standard_gates/rx.py | 8 +++++--- qiskit/circuit/parameterexpression.py | 2 +- .../template_matching/template_substitution.py | 10 ++++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index 7abfdecfd241..ba1897a61038 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -87,11 +87,13 @@ def inverse(self): :math:`RX(\lambda)^{\dagger} = RX(-\lambda)` """ - - ### ntb: TERRIBLE HACK: from qiskit.circuit import ParameterExpression if isinstance(self.params[0], ParameterExpression): - return RXGate(-float(self.params[0]._symbol_expr)) + if self.params[0].parameters: + return RXGate(ParameterExpression(self.params[0]._parameter_symbols, + -self.params[0]._symbol_expr)) + else: + return RXGate(-float(self.params[0]._symbol_expr)) else: return RXGate(-self.params[0]) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index ab7f3314bf82..08f083740d89 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -107,6 +107,7 @@ def bind(self, parameter_values: Dict) -> "ParameterExpression": self._raise_if_passed_unknown_parameters(parameter_values.keys()) self._raise_if_passed_nan(parameter_values) + #import pdb; pdb.set_trace() symbol_values = {} for parameter, value in parameter_values.items(): param_expr = self._parameter_symbols[parameter] @@ -177,7 +178,6 @@ def subs(self, parameter_map: Dict) -> "ParameterExpression": # If new_param is an expr, we'll need to construct a matching sympy expr # but with our sympy symbols instead of theirs. - symbol_map = { self._parameter_symbols[old_param]: new_param._symbol_expr for old_param, new_param in parameter_map.items() diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 26eab0871466..2979fa01897b 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -347,6 +347,8 @@ def _substitution(self): ) self.substitution_list.append(config) + #import pdb; pdb.set_trace() + # Remove incompatible matches. self._remove_impossible() @@ -433,7 +435,6 @@ def run_dag_opt(self): node = group.template_dag_dep.get_node(index) inst = node.op.copy() - #import pdb; pdb.set_trace() dag_dep_opt.add_op_node(inst.inverse(), qargs, cargs) # Add the unmatched gates. @@ -502,16 +503,17 @@ def _attempt_bind(self, template_sublist, circuit_sublist): # Create the fake binding dict and check equations, circ_dict, temp_symbols, sol, fake_bind = [], {}, set(), {}, {} + import pdb; pdb.set_trace() for t_idx, temp_params in enumerate(template_params): if isinstance(temp_params, ParameterExpression): - circ_param_str = ''.join(''.join(str(circuit_params[t_idx]).split('$')).split('\\')) + circ_param_str = str(circuit_params[t_idx]) equations.append(sym.Eq(parse_expr(str(temp_params)), parse_expr(circ_param_str))) for param in temp_params.parameters: temp_symbols.add(param) if isinstance(circuit_params[t_idx], ParameterExpression): for param in circuit_params[t_idx].parameters: - circ_dict[param] = str(param) + circ_dict[param] = sym.Symbol(str(param)) if not temp_symbols: return template_dag_dep @@ -520,7 +522,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): sym_sol = sym.solve(equations, temp_symbols) for key in sym_sol: try: - sol[str(key)] = ParameterExpression(circ_dict, str(sym_sol[key])) + sol[str(key)] = ParameterExpression(circ_dict, sym_sol[key]) except TypeError: return None From 59009db57655816a27d6c6a2774ac6829f72d982 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Mon, 9 Aug 2021 21:52:18 -0400 Subject: [PATCH 18/84] cleaned up some whitespace and removed commented-out lines --- qiskit/circuit/parameterexpression.py | 2 +- .../template_matching/template_substitution.py | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 08f083740d89..084371036a7d 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -107,7 +107,6 @@ def bind(self, parameter_values: Dict) -> "ParameterExpression": self._raise_if_passed_unknown_parameters(parameter_values.keys()) self._raise_if_passed_nan(parameter_values) - #import pdb; pdb.set_trace() symbol_values = {} for parameter, value in parameter_values.items(): param_expr = self._parameter_symbols[parameter] @@ -178,6 +177,7 @@ def subs(self, parameter_map: Dict) -> "ParameterExpression": # If new_param is an expr, we'll need to construct a matching sympy expr # but with our sympy symbols instead of theirs. + symbol_map = { self._parameter_symbols[old_param]: new_param._symbol_expr for old_param, new_param in parameter_map.items() diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 2979fa01897b..48cbbd76bee5 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -269,11 +269,6 @@ def _remove_impossible(self): list_predecessors = [] remove_list = [] - # First remove any scenarios that have parameters in the template. - #for scenario in self.substitution_list: - # if scenario.has_parameters(): - # remove_list.append(scenario) - # Initialize predecessors for each group of matches. for scenario in self.substitution_list: predecessors = set() @@ -347,8 +342,6 @@ def _substitution(self): ) self.substitution_list.append(config) - #import pdb; pdb.set_trace() - # Remove incompatible matches. self._remove_impossible() @@ -511,6 +504,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): for param in temp_params.parameters: temp_symbols.add(param) + if isinstance(circuit_params[t_idx], ParameterExpression): for param in circuit_params[t_idx].parameters: circ_dict[param] = sym.Symbol(str(param)) From 432bb3f4e094cdb865f9449154a5359a520fc61c Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 11 Aug 2021 16:59:08 -0400 Subject: [PATCH 19/84] cleaned up some tox/lint errors --- qiskit/circuit/library/standard_gates/rx.py | 8 ++++++-- qiskit/circuit/parameterexpression.py | 2 +- .../template_matching/template_substitution.py | 14 +++++++------- ...-to-template-substitution-a1379cdbfcc10b5c.yaml | 13 +++++++++++++ 4 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index ba1897a61038..2e974576205e 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -88,10 +88,14 @@ def inverse(self): :math:`RX(\lambda)^{\dagger} = RX(-\lambda)` """ from qiskit.circuit import ParameterExpression + if isinstance(self.params[0], ParameterExpression): if self.params[0].parameters: - return RXGate(ParameterExpression(self.params[0]._parameter_symbols, - -self.params[0]._symbol_expr)) + return RXGate( + ParameterExpression( + self.params[0]._parameter_symbols, -self.params[0]._symbol_expr + ) + ) else: return RXGate(-float(self.params[0]._symbol_expr)) else: diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 084371036a7d..ab7f3314bf82 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -177,7 +177,7 @@ def subs(self, parameter_map: Dict) -> "ParameterExpression": # If new_param is an expr, we'll need to construct a matching sympy expr # but with our sympy symbols instead of theirs. - + symbol_map = { self._parameter_symbols[old_param]: new_param._symbol_expr for old_param, new_param in parameter_map.items() diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 48cbbd76bee5..4b6281557919 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -482,6 +482,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): the parameters bound. If no binding satisfies the parameter constraints, returns None. """ + from qiskit.circuit import Parameter import sympy as sym from sympy.parsing.sympy_parser import parse_expr @@ -495,16 +496,15 @@ def _attempt_bind(self, template_sublist, circuit_sublist): template_params += template_dag_dep.get_node(t_idx).op.params # Create the fake binding dict and check - equations, circ_dict, temp_symbols, sol, fake_bind = [], {}, set(), {}, {} - import pdb; pdb.set_trace() + equations, circ_dict, temp_symbols, sol, fake_bind = [], {}, {}, {}, {} for t_idx, temp_params in enumerate(template_params): if isinstance(temp_params, ParameterExpression): circ_param_str = str(circuit_params[t_idx]) equations.append(sym.Eq(parse_expr(str(temp_params)), parse_expr(circ_param_str))) for param in temp_params.parameters: - temp_symbols.add(param) - + temp_symbols[param] = sym.Symbol(str(param)) + if isinstance(circuit_params[t_idx], ParameterExpression): for param in circuit_params[t_idx].parameters: circ_dict[param] = sym.Symbol(str(param)) @@ -513,7 +513,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): return template_dag_dep # Check compatibility by solving the resulting equation - sym_sol = sym.solve(equations, temp_symbols) + sym_sol = sym.solve(equations, set(temp_symbols.values())) for key in sym_sol: try: sol[str(key)] = ParameterExpression(circ_dict, sym_sol[key]) @@ -523,8 +523,8 @@ def _attempt_bind(self, template_sublist, circuit_sublist): if not sol: return None - for param in temp_symbols: - fake_bind[param] = sol[str(param)] + for key in temp_symbols: + fake_bind[key] = sol[str(key)] for node in template_dag_dep.get_nodes(): bound_params = [] diff --git a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml new file mode 100644 index 000000000000..02e796a8ba26 --- /dev/null +++ b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml @@ -0,0 +1,13 @@ +--- +features: + - | + The Parameter class is now allowed in template optimization. It has been + removed from the _remove_impossible() method for scenarios that are + incompatible with template optimization. Inversion of gates is currently + only implemented for the RXGate(). +issues: + - | + Currently Parameters with names written as LaTeX (i.e., contain dollar + signs '$') are incompatible with the parse_expr() method used by Sympy. + Currently the only gate that can be inverted (as done in the template + optimization algorithm) with a Parameter is the RXGate(). From b308587b0332befe2d003afa56d6261b87ee218a Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 11 Aug 2021 17:23:35 -0400 Subject: [PATCH 20/84] removed unneccessary Parameter import --- .../optimization/template_matching/template_substitution.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 4b6281557919..cb9843b61c85 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -482,7 +482,6 @@ def _attempt_bind(self, template_sublist, circuit_sublist): the parameters bound. If no binding satisfies the parameter constraints, returns None. """ - from qiskit.circuit import Parameter import sympy as sym from sympy.parsing.sympy_parser import parse_expr From 71aa17e8b40b7dcbc2078f6959194c747e74d6eb Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Mon, 16 Aug 2021 15:48:46 -0400 Subject: [PATCH 21/84] bypassed unit test test_unbound_parameters() and re-tox/lint --- test/python/transpiler/test_template_matching.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index ab064ba35d3d..c0a0f63ba061 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -316,6 +316,9 @@ def count_cx(qc): self.assertEqual(count_cx(circuit_out), 2) # One match => two CX gates. np.testing.assert_almost_equal(Operator(circuit_in).data, Operator(circuit_out).data) + @unittest.skip( + "Skipping because the inverse gate is defined as a UnitaryGate that is currently cast to a complex numpy array" + ) def test_unbound_parameters(self): """ Test that partial matches with parameters will not raise errors. From bf590d3274bd102d09c5a63886dcfca8b6fbc65b Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Mon, 16 Aug 2021 16:24:20 -0400 Subject: [PATCH 22/84] fixed one last linting issue --- test/python/transpiler/test_template_matching.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index c0a0f63ba061..b4a38c6610f8 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -317,7 +317,8 @@ def count_cx(qc): np.testing.assert_almost_equal(Operator(circuit_in).data, Operator(circuit_out).data) @unittest.skip( - "Skipping because the inverse gate is defined as a UnitaryGate that is currently cast to a complex numpy array" + "Skipping because the inverse gate is defined as a UnitaryGate that is \ + currently cast to a complex numpy array" ) def test_unbound_parameters(self): """ From 0f5f0d60e722f3e9e02d91293374c5f32312e8c0 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Tue, 17 Aug 2021 11:40:42 -0400 Subject: [PATCH 23/84] fixed cyclic import error --- qiskit/circuit/library/standard_gates/rx.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index 6ae02c7f413f..3257bdb1e726 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -20,7 +20,7 @@ from qiskit.circuit.controlledgate import ControlledGate from qiskit.circuit.gate import Gate from qiskit.circuit.quantumregister import QuantumRegister -from qiskit.circuit.parameterexpression import ParameterValueType +from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType class RXGate(Gate): @@ -95,8 +95,6 @@ def inverse(self): :math:`RX(\lambda)^{\dagger} = RX(-\lambda)` """ - from qiskit.circuit import ParameterExpression - if isinstance(self.params[0], ParameterExpression): if self.params[0].parameters: return RXGate( From bd3f9ed57e07f64fcaea9e50e04df629f240bed4 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Tue, 17 Aug 2021 17:55:20 -0400 Subject: [PATCH 24/84] modified calibration_creator to accept a ParameterExpression containing a float --- .../transpiler/passes/scheduling/calibration_creators.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index 7b3cceb0fea7..0264bc8b3ceb 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -31,6 +31,7 @@ from qiskit.providers import basebackend from qiskit.dagcircuit import DAGOpNode from qiskit.circuit.library.standard_gates import RZXGate +from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.transpiler.basepasses import TransformationPass @@ -174,7 +175,11 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: QiskitError: if the control and target qubits cannot be identified or the backend does not support cx between the qubits. """ - theta = params[0] + if isinstance(params[0], ParameterExpression): + theta = float(params[0]._symbol_expr) + else: + theta = params[0] + q1, q2 = qubits[0], qubits[1] if not self._inst_map.has("cx", qubits): From d9530e6347c29b719a8f8b84a17371e55eb6d69d Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 25 Aug 2021 18:49:47 -0400 Subject: [PATCH 25/84] fixed an mismatch when trying to add calibrations and addressed a conversation in the PR --- qiskit/dagcircuit/dagcircuit.py | 5 ++++- .../passes/scheduling/calibration_creators.py | 11 +++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 883eea1ea51f..d22044830ab9 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -215,7 +215,10 @@ def add_calibration(self, gate, qubits, schedule, params=None): Exception: if the gate is of type string and params is None. """ if isinstance(gate, Gate): - self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule + if isinstance(gate.params[0], ParameterExpression): + self._calibrations[gate.name][(tuple(qubits), tuple([float(gate.params[0]._symbol_expr)]))] = schedule + else: + self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule else: self._calibrations[gate][(tuple(qubits), tuple(params or []))] = schedule diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index 0264bc8b3ceb..d035a6c56380 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -175,10 +175,13 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: QiskitError: if the control and target qubits cannot be identified or the backend does not support cx between the qubits. """ - if isinstance(params[0], ParameterExpression): - theta = float(params[0]._symbol_expr) - else: - theta = params[0] + try: + theta = float(params[0]) + except TypeError: + raise QiskitError( + "This transpilation pass requires all Parameters to be bound." + ) + q1, q2 = qubits[0], qubits[1] From a9e7cdf957c3093450218afb41062ad0ed20872e Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 25 Aug 2021 19:06:01 -0400 Subject: [PATCH 26/84] last tox/lint checks i'm sure ;) --- qiskit/dagcircuit/dagcircuit.py | 4 +++- qiskit/transpiler/passes/scheduling/calibration_creators.py | 6 ++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index d22044830ab9..6c648fc15617 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -216,7 +216,9 @@ def add_calibration(self, gate, qubits, schedule, params=None): """ if isinstance(gate, Gate): if isinstance(gate.params[0], ParameterExpression): - self._calibrations[gate.name][(tuple(qubits), tuple([float(gate.params[0]._symbol_expr)]))] = schedule + self._calibrations[gate.name][ + (tuple(qubits), tuple([float(gate.params[0]._symbol_expr)])) + ] = schedule else: self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule else: diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index d035a6c56380..5f84e0292361 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -31,7 +31,6 @@ from qiskit.providers import basebackend from qiskit.dagcircuit import DAGOpNode from qiskit.circuit.library.standard_gates import RZXGate -from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.transpiler.basepasses import TransformationPass @@ -177,11 +176,10 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: """ try: theta = float(params[0]) - except TypeError: + except TypeError as ex: raise QiskitError( "This transpilation pass requires all Parameters to be bound." - ) - + ) from ex q1, q2 = qubits[0], qubits[1] From 443a6c96dd0937a00c13740f43f0f64a5b5c457b Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Tue, 31 Aug 2021 13:02:32 -0400 Subject: [PATCH 27/84] now params comes from node_op argument --- qiskit/transpiler/passes/calibration/builders.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/calibration/builders.py b/qiskit/transpiler/passes/calibration/builders.py index c20897a471f3..9b95219c4e4c 100644 --- a/qiskit/transpiler/passes/calibration/builders.py +++ b/qiskit/transpiler/passes/calibration/builders.py @@ -198,12 +198,12 @@ def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, does not support cx between the qubits. """ try: - theta = float(params[0]) + theta = float(node_op.params[0]) except TypeError as ex: raise QiskitError( "This transpilation pass requires all Parameters to be bound." ) from ex - + q1, q2 = qubits[0], qubits[1] if not self._inst_map.has("cx", qubits): From 168c3cc1878e581e65f1d4cd3aea9e54df81414b Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Fri, 3 Sep 2021 19:06:09 -0400 Subject: [PATCH 28/84] handling error in case gate parameters is empty in dagcircuit.py --- qiskit/dagcircuit/dagcircuit.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 13a0855237a4..2c1f778ad147 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -215,11 +215,14 @@ def add_calibration(self, gate, qubits, schedule, params=None): Exception: if the gate is of type string and params is None. """ if isinstance(gate, Gate): - if isinstance(gate.params[0], ParameterExpression): - self._calibrations[gate.name][ - (tuple(qubits), tuple([float(gate.params[0]._symbol_expr)])) - ] = schedule - else: + try: + if isinstance(gate.params[0], ParameterExpression): + self._calibrations[gate.name][ + (tuple(qubits), tuple([float(gate.params[0]._symbol_expr)])) + ] = schedule + else: + self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule + except IndexError: self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule else: self._calibrations[gate][(tuple(qubits), tuple(params or []))] = schedule From fa51c3c933b6a33801fa7ce5505c48941ad3e60b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Pracht?= Date: Tue, 5 Oct 2021 14:24:00 +0200 Subject: [PATCH 29/84] Fix template matching for parameters with LaTeX name. --- qiskit/circuit/parameterexpression.py | 5 +++++ .../template_matching/template_substitution.py | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 2d11f81ee4df..c217d499fff4 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -514,6 +514,11 @@ def is_real(self): return False return True + def get_expr(self): + from sympy import sympify + + return sympify(self._symbol_expr) + # Redefine the type so external imports get an evaluated reference; Sphinx needs this to understand # the type hints. diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index cb9843b61c85..cf07578b77be 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -498,8 +498,11 @@ def _attempt_bind(self, template_sublist, circuit_sublist): equations, circ_dict, temp_symbols, sol, fake_bind = [], {}, {}, {}, {} for t_idx, temp_params in enumerate(template_params): if isinstance(temp_params, ParameterExpression): - circ_param_str = str(circuit_params[t_idx]) - equations.append(sym.Eq(parse_expr(str(temp_params)), parse_expr(circ_param_str))) + if isinstance(circuit_params[t_idx], ParameterExpression): + circ_param_sym = circuit_params[t_idx].get_expr() + else: + circ_param_sym = parse_expr(str(circuit_params[t_idx])) + equations.append(sym.Eq(temp_params.get_expr(), circ_param_sym)) for param in temp_params.parameters: temp_symbols[param] = sym.Symbol(str(param)) From 206582659ab6474783bcc189b7daaa9c970e365f Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 20 Oct 2021 15:54:20 -0400 Subject: [PATCH 30/84] added missing docstring --- qiskit/circuit/parameterexpression.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index dce6240fd41b..dbfa272b4eb9 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -121,7 +121,7 @@ def bind(self, parameter_values: Dict) -> "ParameterExpression": for parameter, value in parameter_values.items(): param_expr = self._parameter_symbols[parameter] symbol_values[param_expr] = value - + import pdb; pdb.set_trace() bound_symbol_expr = self._symbol_expr.subs(symbol_values) # Don't use sympy.free_symbols to count remaining parameters here. @@ -527,6 +527,7 @@ def is_real(self): return True def get_expr(self): + """Return symbolic expression from sympy""" from sympy import sympify return sympify(self._symbol_expr) From a8b9b0590bbcb92f07a73555ce0f5e2a3ffd8d10 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 20 Oct 2021 17:55:27 -0400 Subject: [PATCH 31/84] removed pdb set_trace --- qiskit/circuit/parameterexpression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index dbfa272b4eb9..cc4f67906f8f 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -121,7 +121,7 @@ def bind(self, parameter_values: Dict) -> "ParameterExpression": for parameter, value in parameter_values.items(): param_expr = self._parameter_symbols[parameter] symbol_values[param_expr] = value - import pdb; pdb.set_trace() + bound_symbol_expr = self._symbol_expr.subs(symbol_values) # Don't use sympy.free_symbols to count remaining parameters here. From bd6dda4aff997562ccc4bb13b212d5510df016cf Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Tue, 2 Nov 2021 12:52:45 -0400 Subject: [PATCH 32/84] made changes requested in PR 6899 --- qiskit/dagcircuit/dagcircuit.py | 4 ++-- qiskit/transpiler/passes/calibration/builders.py | 5 +++-- .../template_matching/template_substitution.py | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 0087c9b61ba6..b99ad9b6b898 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -222,8 +222,8 @@ def add_calibration(self, gate, qubits, schedule, params=None): self._calibrations[gate.name][ (tuple(qubits), tuple([float(gate.params[0]._symbol_expr)])) ] = schedule - else: - self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule + else: + self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule except IndexError: self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule else: diff --git a/qiskit/transpiler/passes/calibration/builders.py b/qiskit/transpiler/passes/calibration/builders.py index 9b95219c4e4c..66f5aa1dfd18 100644 --- a/qiskit/transpiler/passes/calibration/builders.py +++ b/qiskit/transpiler/passes/calibration/builders.py @@ -194,8 +194,9 @@ def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, schedule: The calibration schedule for the RZXGate(theta). Raises: - QiskitError: if the control and target qubits cannot be identified or the backend - does not support cx between the qubits. + QiskitError: If all Parameters are not bound, if the control and target + qubits cannot be identified, or the backend does not support cx between + the qubits. """ try: theta = float(node_op.params[0]) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index cf07578b77be..62748d1b151c 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -496,15 +496,15 @@ def _attempt_bind(self, template_sublist, circuit_sublist): # Create the fake binding dict and check equations, circ_dict, temp_symbols, sol, fake_bind = [], {}, {}, {}, {} - for t_idx, temp_params in enumerate(template_params): - if isinstance(temp_params, ParameterExpression): + for t_idx, template_params in enumerate(template_params): + if isinstance(template_params, ParameterExpression): if isinstance(circuit_params[t_idx], ParameterExpression): circ_param_sym = circuit_params[t_idx].get_expr() else: circ_param_sym = parse_expr(str(circuit_params[t_idx])) - equations.append(sym.Eq(temp_params.get_expr(), circ_param_sym)) + equations.append(sym.Eq(template_params.get_expr(), circ_param_sym)) - for param in temp_params.parameters: + for param in template_params.parameters: temp_symbols[param] = sym.Symbol(str(param)) if isinstance(circuit_params[t_idx], ParameterExpression): From 6ea9d90924fc94ad86fe122ac281aeb294eabf94 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Tue, 2 Nov 2021 12:53:08 -0400 Subject: [PATCH 33/84] made changes requested in PR 6899 #2 --- qiskit/circuit/parameterexpression.py | 2 +- .../optimization/template_matching/template_substitution.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index cc4f67906f8f..8d50e9047849 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -526,7 +526,7 @@ def is_real(self): return False return True - def get_expr(self): + def get_sympy_expr(self): """Return symbolic expression from sympy""" from sympy import sympify diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 62748d1b151c..d6fed977e3b3 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -499,10 +499,10 @@ def _attempt_bind(self, template_sublist, circuit_sublist): for t_idx, template_params in enumerate(template_params): if isinstance(template_params, ParameterExpression): if isinstance(circuit_params[t_idx], ParameterExpression): - circ_param_sym = circuit_params[t_idx].get_expr() + circ_param_sym = circuit_params[t_idx].get_sympy_expr() else: circ_param_sym = parse_expr(str(circuit_params[t_idx])) - equations.append(sym.Eq(template_params.get_expr(), circ_param_sym)) + equations.append(sym.Eq(template_params.get_sympy_expr(), circ_param_sym)) for param in template_params.parameters: temp_symbols[param] = sym.Symbol(str(param)) From cebce7466b67d8082875015328c6024a55046028 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Tue, 2 Nov 2021 15:07:48 -0400 Subject: [PATCH 34/84] remembered to tighten try/except handling --- qiskit/dagcircuit/dagcircuit.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 733855fc2294..e71a24f07619 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -222,8 +222,6 @@ def add_calibration(self, gate, qubits, schedule, params=None): self._calibrations[gate.name][ (tuple(qubits), tuple([float(gate.params[0]._symbol_expr)])) ] = schedule - else: - self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule except IndexError: self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule else: From 8b6bf37c5bf384bb2930e322ee23e9c7bee8d2d6 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 3 Nov 2021 13:53:49 -0400 Subject: [PATCH 35/84] finished making changes requested in PR 6899 --- qiskit/circuit/library/standard_gates/rx.py | 11 ++- qiskit/dagcircuit/dagcircuit.py | 12 ++- ...emplate-substitution-a1379cdbfcc10b5c.yaml | 76 +++++++++++++++++++ 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index 3257bdb1e726..65118dafefd2 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -97,13 +97,12 @@ def inverse(self): """ if isinstance(self.params[0], ParameterExpression): if self.params[0].parameters: - return RXGate( - ParameterExpression( - self.params[0]._parameter_symbols, -self.params[0]._symbol_expr - ) - ) + param_dict = {} + for param in self.params[0].parameters: + param_dict[param] = param.name + return RXGate(ParameterExpression(param_dict, -self.params[0].get_sympy_expr())) else: - return RXGate(-float(self.params[0]._symbol_expr)) + return RXGate(-float(self.params[0].get_sympy_expr())) else: return RXGate(-self.params[0]) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index e71a24f07619..6f60a87a4cdd 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -218,11 +218,15 @@ def add_calibration(self, gate, qubits, schedule, params=None): """ if isinstance(gate, Gate): try: - if isinstance(gate.params[0], ParameterExpression): - self._calibrations[gate.name][ - (tuple(qubits), tuple([float(gate.params[0]._symbol_expr)])) - ] = schedule + gate_has_param_exp = isinstance(gate.params[0], ParameterExpression) except IndexError: + gate_has_param_exp = False + + if gate_has_param_exp: + self._calibrations[gate.name][ + (tuple(qubits), tuple([float(gate.params[0]._symbol_expr)])) + ] = schedule + else: self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule else: self._calibrations[gate][(tuple(qubits), tuple(params or []))] = schedule diff --git a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml index 02e796a8ba26..8f39c336a8c4 100644 --- a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml +++ b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml @@ -11,3 +11,79 @@ issues: signs '$') are incompatible with the parse_expr() method used by Sympy. Currently the only gate that can be inverted (as done in the template optimization algorithm) with a Parameter is the RXGate(). +other: + - | + An illustrative example of this PR is modeled after the addition of + calibrations for template optimization #5752: + + .. code-block:: + + from qiskit import QuantumCircuit, transpile, schedule, IBMQ + from qiskit.circuit import Parameter + + from qiskit.transpiler import PassManager + from qiskit.transpiler.passes import TemplateOptimization + + # New contributions to the template optimization + from qiskit.transpiler.passes.scheduling import RZXCalibrationBuilder, rzx_templates + + IBMQ.load_account() + provider = IBMQ.get_provider(...) + backend = provider.get_backend('ibmq_casablanca') + + phi = Parameter('φ') + + qc = QuantumCircuit(2) + qc.cx(0,1) + qc.p(2*phi, 1) + qc.cx(0,1) + print('Original circuit:') + print(qc) + + pass_ = TemplateOptimization(**rzx_templates(['zz2'])) + qc_cz = PassManager(pass_).run(qc) + print('ZX based circuit:') + print(qc_cz) + + # Add the calibratiocns + pass_ = RZXCalibrationBuilder(backend) + cal_qc = PassManager(pass_).run(qc_cz.bind_parameters({phi: 0.12})) + + # Transpile to the backend basis gates + cal_qct = transpile(cal_qc, backend) + qct = transpile(qc.bind_parameters({phi: 0.12}), backend) + + # Compare the schedule durations + print('Duration of schedule with the calibration:') + print(schedule(cal_qct, backend).duration) + print('Duration of standard with two CNOT gates:') + print(schedule(qct, backend).duration) + + outputs + + .. parsed-literal:: + + Original circuit: + + q_0: ──■──────────────■── + ┌─┴─┐┌────────┐┌─┴─┐ + q_1: ┤ X ├┤ P(2*φ) ├┤ X ├ + └───┘└────────┘└───┘ + ZX based circuit: + ┌─────────────┐ » + q_0: ────────────────────────────────────┤0 ├────────────» + ┌──────────┐┌──────────┐┌──────────┐│ Rzx(2.0*φ) │┌──────────┐» + q_1: ┤ Rz(-π/2) ├┤ Rx(-π/2) ├┤ Rz(-π/2) ├┤1 ├┤ Rx(-2*φ) ├» + └──────────┘└──────────┘└──────────┘└─────────────┘└──────────┘» + « + «q_0: ──────────────────────────────────────────────── + « ┌──────────┐┌──────────┐┌──────────┐┌──────────┐ + «q_1: ┤ Rz(-π/2) ├┤ Rx(-π/2) ├┤ Rz(-π/2) ├┤ P(2.0*φ) ├ + « └──────────┘└──────────┘└──────────┘└──────────┘ + Duration of schedule with the calibration: + 1600 + Duration of standard with two CNOT gates: + 6848 + + Note that the Parameters/ParameterExpressions must be bound before the + RZXCalibrationBuilder can define calibrations for the RZXGates. From 7721f23e2dd4e598429a6b0b379e977164bd8124 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 3 Nov 2021 16:01:56 -0400 Subject: [PATCH 36/84] fixed remaining linting issue --- ...emplate-substitution-a1379cdbfcc10b5c.yaml | 130 +++++++++--------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml index 8f39c336a8c4..eb5b4d2b5368 100644 --- a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml +++ b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml @@ -18,72 +18,72 @@ other: .. code-block:: - from qiskit import QuantumCircuit, transpile, schedule, IBMQ - from qiskit.circuit import Parameter - - from qiskit.transpiler import PassManager - from qiskit.transpiler.passes import TemplateOptimization - - # New contributions to the template optimization - from qiskit.transpiler.passes.scheduling import RZXCalibrationBuilder, rzx_templates - - IBMQ.load_account() - provider = IBMQ.get_provider(...) - backend = provider.get_backend('ibmq_casablanca') - - phi = Parameter('φ') - - qc = QuantumCircuit(2) - qc.cx(0,1) - qc.p(2*phi, 1) - qc.cx(0,1) - print('Original circuit:') - print(qc) - - pass_ = TemplateOptimization(**rzx_templates(['zz2'])) - qc_cz = PassManager(pass_).run(qc) - print('ZX based circuit:') - print(qc_cz) - - # Add the calibratiocns - pass_ = RZXCalibrationBuilder(backend) - cal_qc = PassManager(pass_).run(qc_cz.bind_parameters({phi: 0.12})) - - # Transpile to the backend basis gates - cal_qct = transpile(cal_qc, backend) - qct = transpile(qc.bind_parameters({phi: 0.12}), backend) - - # Compare the schedule durations - print('Duration of schedule with the calibration:') - print(schedule(cal_qct, backend).duration) - print('Duration of standard with two CNOT gates:') - print(schedule(qct, backend).duration) - + from qiskit import QuantumCircuit, transpile, schedule, IBMQ + from qiskit.circuit import Parameter + + from qiskit.transpiler import PassManager + from qiskit.transpiler.passes import TemplateOptimization + + # New contributions to the template optimization + from qiskit.transpiler.passes.scheduling import RZXCalibrationBuilder, rzx_templates + + IBMQ.load_account() + provider = IBMQ.get_provider(...) + backend = provider.get_backend('ibmq_casablanca') + + phi = Parameter('φ') + + qc = QuantumCircuit(2) + qc.cx(0,1) + qc.p(2*phi, 1) + qc.cx(0,1) + print('Original circuit:') + print(qc) + + pass_ = TemplateOptimization(**rzx_templates(['zz2'])) + qc_cz = PassManager(pass_).run(qc) + print('ZX based circuit:') + print(qc_cz) + + # Add the calibratiocns + pass_ = RZXCalibrationBuilder(backend) + cal_qc = PassManager(pass_).run(qc_cz.bind_parameters({phi: 0.12})) + + # Transpile to the backend basis gates + cal_qct = transpile(cal_qc, backend) + qct = transpile(qc.bind_parameters({phi: 0.12}), backend) + + # Compare the schedule durations + print('Duration of schedule with the calibration:') + print(schedule(cal_qct, backend).duration) + print('Duration of standard with two CNOT gates:') + print(schedule(qct, backend).duration) + outputs .. parsed-literal:: - Original circuit: - - q_0: ──■──────────────■── - ┌─┴─┐┌────────┐┌─┴─┐ - q_1: ┤ X ├┤ P(2*φ) ├┤ X ├ - └───┘└────────┘└───┘ - ZX based circuit: - ┌─────────────┐ » - q_0: ────────────────────────────────────┤0 ├────────────» - ┌──────────┐┌──────────┐┌──────────┐│ Rzx(2.0*φ) │┌──────────┐» - q_1: ┤ Rz(-π/2) ├┤ Rx(-π/2) ├┤ Rz(-π/2) ├┤1 ├┤ Rx(-2*φ) ├» - └──────────┘└──────────┘└──────────┘└─────────────┘└──────────┘» - « - «q_0: ──────────────────────────────────────────────── - « ┌──────────┐┌──────────┐┌──────────┐┌──────────┐ - «q_1: ┤ Rz(-π/2) ├┤ Rx(-π/2) ├┤ Rz(-π/2) ├┤ P(2.0*φ) ├ - « └──────────┘└──────────┘└──────────┘└──────────┘ - Duration of schedule with the calibration: - 1600 - Duration of standard with two CNOT gates: - 6848 - - Note that the Parameters/ParameterExpressions must be bound before the - RZXCalibrationBuilder can define calibrations for the RZXGates. + Original circuit: + + q_0: ──■──────────────■── + ┌─┴─┐┌────────┐┌─┴─┐ + q_1: ┤ X ├┤ P(2*φ) ├┤ X ├ + └───┘└────────┘└───┘ + ZX based circuit: + ┌─────────────┐ » + q_0: ────────────────────────────────────┤0 ├────────────» + ┌──────────┐┌──────────┐┌──────────┐│ Rzx(2.0*φ) │┌──────────┐» + q_1: ┤ Rz(-π/2) ├┤ Rx(-π/2) ├┤ Rz(-π/2) ├┤1 ├┤ Rx(-2*φ) ├» + └──────────┘└──────────┘└──────────┘└─────────────┘└──────────┘» + « + «q_0: ──────────────────────────────────────────────── + « ┌──────────┐┌──────────┐┌──────────┐┌──────────┐ + «q_1: ┤ Rz(-π/2) ├┤ Rx(-π/2) ├┤ Rz(-π/2) ├┤ P(2.0*φ) ├ + « └──────────┘└──────────┘└──────────┘└──────────┘ + Duration of schedule with the calibration: + 1600 + Duration of standard with two CNOT gates: + 6848 + + Note that the Parameters/ParameterExpressions must be bound before the + RZXCalibrationBuilder can define calibrations for the RZXGates. From 5e313981bdaed8b35bd8364e5ef0a48ee4ceb236 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 3 Nov 2021 17:21:52 -0400 Subject: [PATCH 37/84] added comment about templates working for parameterized RZXGates and RXGates --- ...ers-to-template-substitution-a1379cdbfcc10b5c.yaml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml index eb5b4d2b5368..7fe05d8122f5 100644 --- a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml +++ b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml @@ -1,14 +1,13 @@ --- features: - | - The Parameter class is now allowed in template optimization. It has been - removed from the _remove_impossible() method for scenarios that are - incompatible with template optimization. Inversion of gates is currently - only implemented for the RXGate(). + The Parameter class is now allowed in template optimization if the + parameterized gates are RZXGates and RXGates. It has been removed from + the _remove_impossible() method for scenarios that are incompatible + with template optimization. Inversion of gates is currently only + implemented for the RXGate(). issues: - | - Currently Parameters with names written as LaTeX (i.e., contain dollar - signs '$') are incompatible with the parse_expr() method used by Sympy. Currently the only gate that can be inverted (as done in the template optimization algorithm) with a Parameter is the RXGate(). other: From 29733397f3511a9efb7bcd5948296b553745a14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Pracht?= Date: Fri, 19 Nov 2021 18:32:31 +0100 Subject: [PATCH 38/84] Fix test unbound parameters --- qiskit/circuit/parameterexpression.py | 7 ++++++- qiskit/extensions/unitary.py | 21 ++++++++++++++----- .../transpiler/test_template_matching.py | 8 +------ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index c217d499fff4..5752e59aa0ce 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -35,6 +35,11 @@ ParameterValueType = Union["ParameterExpression", float] +class ParameterTypeError(TypeError): + """The type error throw by parameter if we try to cast to number unbind parameter.""" + pass + + class ParameterExpression: """ParameterExpression class to enable creating expressions of Parameters.""" @@ -427,7 +432,7 @@ def __float__(self): def __complex__(self): if self.parameters: - raise TypeError( + raise ParameterTypeError( "ParameterExpression with unbound parameters ({}) " "cannot be cast to a complex.".format(self.parameters) ) diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index f43ccb3b8843..945f71a2ff1d 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -29,6 +29,7 @@ from qiskit.quantum_info.synthesis.one_qubit_decompose import OneQubitEulerDecomposer from qiskit.quantum_info.synthesis.two_qubit_decompose import two_qubit_cnot_decompose from qiskit.extensions.exceptions import ExtensionError +from qiskit.circuit.parameterexpression import ParameterTypeError _DECOMPOSER1Q = OneQubitEulerDecomposer("U3") @@ -55,11 +56,21 @@ def __init__(self, data, label=None): # the object to an Operator so that we can extract the underlying # numpy matrix from `Operator.data`. data = data.to_operator().data - # Convert to numpy array in case not already an array - data = numpy.array(data, dtype=complex) - # Check input is unitary - if not is_unitary_matrix(data): - raise ExtensionError("Input matrix is not unitary.") + + # Check if there is any unbound parameter in array + can_check_unitary = True + try: + # Convert to numpy array in case not already an array + data = numpy.array(data, dtype=complex) + except ParameterTypeError: + can_check_unitary = False + + # We only check if input is unitary for bounded parameters + if can_check_unitary: + # Check input is unitary + if not is_unitary_matrix(data): + raise ExtensionError("Input matrix is not unitary.") + # Check input is N-qubit matrix input_dim, output_dim = data.shape num_qubits = int(numpy.log2(input_dim)) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index b4a38c6610f8..cbf321847b34 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -316,10 +316,6 @@ def count_cx(qc): self.assertEqual(count_cx(circuit_out), 2) # One match => two CX gates. np.testing.assert_almost_equal(Operator(circuit_in).data, Operator(circuit_out).data) - @unittest.skip( - "Skipping because the inverse gate is defined as a UnitaryGate that is \ - currently cast to a complex numpy array" - ) def test_unbound_parameters(self): """ Test that partial matches with parameters will not raise errors. @@ -359,9 +355,7 @@ def template(): pass_ = TemplateOptimization(template_list=[template()]) circuit_out = PassManager(pass_).run(circuit_in) - # This template will not fully match as long as gates with parameters do not - # commute with any other gates in the DAG dependency. - self.assertEqual(circuit_out.count_ops().get("cx", 0), 2) + self.assertEqual(circuit_out.count_ops().get("cx", 0), 0) if __name__ == "__main__": From bb1c797d2f0d1e779d1700479cd146cfa5a4b9e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Pracht?= Date: Tue, 23 Nov 2021 21:35:11 +0100 Subject: [PATCH 39/84] Check if matrix with Parameter is unitary --- qiskit/circuit/parameter.py | 5 +-- qiskit/circuit/parameterexpression.py | 2 +- qiskit/extensions/unitary.py | 34 +++++++++++++++---- .../template_substitution.py | 4 +-- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/qiskit/circuit/parameter.py b/qiskit/circuit/parameter.py index 8e4a503cc040..98abd5d9c59e 100644 --- a/qiskit/circuit/parameter.py +++ b/qiskit/circuit/parameter.py @@ -59,7 +59,8 @@ def __init__(self, name: str): if not HAS_SYMENGINE: from sympy import Symbol - symbol = Symbol(name) + # Parameter can only be real + symbol = Symbol(name, real=True) else: symbol = symengine.Symbol(name) super().__init__(symbol_map={self: symbol}, expr=symbol) @@ -104,7 +105,7 @@ def __setstate__(self, state): if not HAS_SYMENGINE: from sympy import Symbol - symbol = Symbol(self._name) + symbol = Symbol(self._name, real=True) else: symbol = symengine.Symbol(self._name) super().__init__(symbol_map={self: symbol}, expr=symbol) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 5752e59aa0ce..0a072af0eef4 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -171,7 +171,7 @@ def subs(self, parameter_map: Dict) -> "ParameterExpression": else: from sympy import Symbol - new_parameter_symbols = {p: Symbol(p.name) for p in inbound_parameters} + new_parameter_symbols = {p: Symbol(p.name, real=True) for p in inbound_parameters} # Include existing parameters in self not set to be replaced. new_parameter_symbols.update( diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index 945f71a2ff1d..b1b87baede0e 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -16,6 +16,7 @@ from collections import OrderedDict import numpy +import sympy as sp from qiskit.circuit import Gate, ControlledGate from qiskit.circuit import QuantumCircuit @@ -29,11 +30,27 @@ from qiskit.quantum_info.synthesis.one_qubit_decompose import OneQubitEulerDecomposer from qiskit.quantum_info.synthesis.two_qubit_decompose import two_qubit_cnot_decompose from qiskit.extensions.exceptions import ExtensionError -from qiskit.circuit.parameterexpression import ParameterTypeError +from qiskit.circuit.parameterexpression import ParameterTypeError, ParameterExpression _DECOMPOSER1Q = OneQubitEulerDecomposer("U3") +def to_sympy_matrix(numpy_matrix): + """Convert (numpy) matrix to sympy matrix.""" + matrix = [] + i = 0 + for row in numpy_matrix: + nrow = [] + i += 1 + for e in row: + if isinstance(e, ParameterExpression): + e = e.get_expr() + nrow.append(e) + matrix.append(nrow) + + return sp.Matrix(matrix), i + + class UnitaryGate(Gate): """Class for representing unitary gates""" @@ -58,18 +75,23 @@ def __init__(self, data, label=None): data = data.to_operator().data # Check if there is any unbound parameter in array - can_check_unitary = True try: # Convert to numpy array in case not already an array data = numpy.array(data, dtype=complex) - except ParameterTypeError: - can_check_unitary = False - # We only check if input is unitary for bounded parameters - if can_check_unitary: # Check input is unitary if not is_unitary_matrix(data): raise ExtensionError("Input matrix is not unitary.") + except ParameterTypeError: + from sympy.physics.quantum import Dagger + matrix, matrix_dim = to_sympy_matrix(data) + iden = sp.sympify(matrix * Dagger(matrix)) + + if iden != sp.eye(matrix_dim): + # There may be a case when there is still a parameter and + # we are not quite sure if this is unitary or not. + # But just in case we throw exception :) + raise ExtensionError("Input matrix is not unitary.") # Check input is N-qubit matrix input_dim, output_dim = data.shape diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index cf07578b77be..037f79898819 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -505,11 +505,11 @@ def _attempt_bind(self, template_sublist, circuit_sublist): equations.append(sym.Eq(temp_params.get_expr(), circ_param_sym)) for param in temp_params.parameters: - temp_symbols[param] = sym.Symbol(str(param)) + temp_symbols[param] = sym.Symbol(str(param), real=True) if isinstance(circuit_params[t_idx], ParameterExpression): for param in circuit_params[t_idx].parameters: - circ_dict[param] = sym.Symbol(str(param)) + circ_dict[param] = sym.Symbol(str(param), real=True) if not temp_symbols: return template_dag_dep From 9260cf4bb15b0e0dc783f9e9ba53c0314a5c6b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Pracht?= Date: Wed, 24 Nov 2021 10:11:40 +0100 Subject: [PATCH 40/84] Fix merge issue --- qiskit/extensions/unitary.py | 2 +- .../optimization/template_matching/template_substitution.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index 86a19c858426..07d4945d2e3f 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -45,7 +45,7 @@ def to_sympy_matrix(numpy_matrix): i += 1 for e in row: if isinstance(e, ParameterExpression): - e = e.get_expr() + e = e.get_sympy_expr() nrow.append(e) matrix.append(nrow) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index fd48127a1f5d..801bae565700 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -504,10 +504,9 @@ def _attempt_bind(self, template_sublist, circuit_sublist): circ_param_sym = parse_expr(str(circuit_params[t_idx])) equations.append(sym.Eq(template_params.get_sympy_expr(), circ_param_sym)) - for param in temp_params.parameters: + for param in template_params.parameters: temp_symbols[param] = sym.Symbol(str(param), real=True) - if isinstance(circuit_params[t_idx], ParameterExpression): for param in circuit_params[t_idx].parameters: circ_dict[param] = sym.Symbol(str(param), real=True) From f50c4e62865915a5fc2f0e4af4ec979f501ecbf7 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 15 Dec 2021 20:08:36 -0400 Subject: [PATCH 41/84] removed real=True in two symbol Symbol expressions, which was messing up the solver for some reason. --- .../optimization/template_matching/template_substitution.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 801bae565700..d6fed977e3b3 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -505,11 +505,11 @@ def _attempt_bind(self, template_sublist, circuit_sublist): equations.append(sym.Eq(template_params.get_sympy_expr(), circ_param_sym)) for param in template_params.parameters: - temp_symbols[param] = sym.Symbol(str(param), real=True) + temp_symbols[param] = sym.Symbol(str(param)) if isinstance(circuit_params[t_idx], ParameterExpression): for param in circuit_params[t_idx].parameters: - circ_dict[param] = sym.Symbol(str(param), real=True) + circ_dict[param] = sym.Symbol(str(param)) if not temp_symbols: return template_dag_dep From e32d03834960044d9fb57246dfccc80a256f69cc Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 15 Dec 2021 20:22:32 -0400 Subject: [PATCH 42/84] generalized to iterate over parameters, and removed reference to private member --- qiskit/dagcircuit/dagcircuit.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 12a24b155484..36d9e645f39c 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -216,17 +216,17 @@ def add_calibration(self, gate, qubits, schedule, params=None): Exception: if the gate is of type string and params is None. """ if isinstance(gate, Gate): - try: - gate_has_param_exp = isinstance(gate.params[0], ParameterExpression) - except IndexError: - gate_has_param_exp = False - - if gate_has_param_exp: - self._calibrations[gate.name][ - (tuple(qubits), tuple([float(gate.params[0]._symbol_expr)])) - ] = schedule - else: - self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule + for param in gate.params: + try: + gate_has_param_exp = isinstance(param, ParameterExpression) + except IndexError: + gate_has_param_exp = False + + if gate_has_param_exp: + self._calibrations[gate.name][(tuple(qubits), + tuple([float(param)]))] = schedule + else: + self._calibrations[gate.name][(tuple(qubits), tuple(param))] = schedule else: self._calibrations[gate][(tuple(qubits), tuple(params or []))] = schedule From c21fdf111eecd284f9e6e8f60504c444c55926a9 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 15 Dec 2021 20:23:25 -0400 Subject: [PATCH 43/84] modified .get_sympy_expr() to use symengine if possible --- qiskit/circuit/parameterexpression.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index dbfaf3c3aa14..3e4c277593de 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -538,10 +538,12 @@ def is_real(self): return True def get_sympy_expr(self): - """Return symbolic expression from sympy""" - from sympy import sympify - - return sympify(self._symbol_expr) + """Return symbolic expression from sympy/symengine""" + if HAS_SYMENGINE: + return symengine.sympify(self._symbol_expr) + else: + from sympy import sympify + return sympify(self._symbol_expr) # Redefine the type so external imports get an evaluated reference; Sphinx needs this to understand From bc42afb93debaeb0f33be603d670ef5c2e66e434 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 15 Dec 2021 20:34:28 -0400 Subject: [PATCH 44/84] made the negation of the RXGate() much less verbose --- qiskit/circuit/library/standard_gates/rx.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index 65118dafefd2..c56ce4f70235 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -95,16 +95,7 @@ def inverse(self): :math:`RX(\lambda)^{\dagger} = RX(-\lambda)` """ - if isinstance(self.params[0], ParameterExpression): - if self.params[0].parameters: - param_dict = {} - for param in self.params[0].parameters: - param_dict[param] = param.name - return RXGate(ParameterExpression(param_dict, -self.params[0].get_sympy_expr())) - else: - return RXGate(-float(self.params[0].get_sympy_expr())) - else: - return RXGate(-self.params[0]) + return RXGate(-self.params[0]) def __array__(self, dtype=None): """Return a numpy.array for the RX gate.""" From cce7b655f9637bf14462d5771b6b74d495a0a215 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Thu, 16 Dec 2021 09:23:15 -0400 Subject: [PATCH 45/84] working thru tox/lint checks --- qiskit/circuit/library/standard_gates/rx.py | 2 +- qiskit/circuit/parameterexpression.py | 2 ++ qiskit/dagcircuit/dagcircuit.py | 24 +++++++++++---------- qiskit/extensions/unitary.py | 23 +++++++++++--------- qiskit/test/base.py | 1 + 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index c56ce4f70235..062642097737 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -20,7 +20,7 @@ from qiskit.circuit.controlledgate import ControlledGate from qiskit.circuit.gate import Gate from qiskit.circuit.quantumregister import QuantumRegister -from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType +from qiskit.circuit.parameterexpression import ParameterValueType class RXGate(Gate): diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 3e4c277593de..2eb57bc4e70d 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -37,6 +37,7 @@ class ParameterTypeError(TypeError): """The type error throw by parameter if we try to cast to number unbind parameter.""" + pass @@ -543,6 +544,7 @@ def get_sympy_expr(self): return symengine.sympify(self._symbol_expr) else: from sympy import sympify + return sympify(self._symbol_expr) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 36d9e645f39c..0d183291ccd6 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -216,17 +216,19 @@ def add_calibration(self, gate, qubits, schedule, params=None): Exception: if the gate is of type string and params is None. """ if isinstance(gate, Gate): - for param in gate.params: - try: - gate_has_param_exp = isinstance(param, ParameterExpression) - except IndexError: - gate_has_param_exp = False - - if gate_has_param_exp: - self._calibrations[gate.name][(tuple(qubits), - tuple([float(param)]))] = schedule - else: - self._calibrations[gate.name][(tuple(qubits), tuple(param))] = schedule + try: + for param in gate.params: + try: + gate_has_param_exp = isinstance(param, ParameterExpression) + except IndexError: + gate_has_param_exp = False + + if gate_has_param_exp: + self._calibrations[gate.name][(tuple(qubits), tuple([float(param)]))] = schedule + else: + self._calibrations[gate.name][(tuple(qubits), tuple(param))] = schedule + except: + self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule else: self._calibrations[gate][(tuple(qubits), tuple(params or []))] = schedule diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index 07d4945d2e3f..520414517e71 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -16,7 +16,6 @@ from collections import OrderedDict import numpy -import sympy as sp from qiskit.circuit import Gate, ControlledGate from qiskit.circuit import QuantumCircuit @@ -43,13 +42,15 @@ def to_sympy_matrix(numpy_matrix): for row in numpy_matrix: nrow = [] i += 1 - for e in row: - if isinstance(e, ParameterExpression): - e = e.get_sympy_expr() - nrow.append(e) + for expr in row: + if isinstance(expr, ParameterExpression): + expr = expr.get_sympy_expr() + nrow.append(expr) matrix.append(nrow) - return sp.Matrix(matrix), i + from sympy import Matrix + + return Matrix(matrix), i class UnitaryGate(Gate): @@ -83,16 +84,18 @@ def __init__(self, data, label=None): # Check input is unitary if not is_unitary_matrix(data): raise ExtensionError("Input matrix is not unitary.") - except ParameterTypeError: + except ParameterTypeError as ex: + from sympy import eye, sympify from sympy.physics.quantum import Dagger + matrix, matrix_dim = to_sympy_matrix(data) - iden = sp.sympify(matrix * Dagger(matrix)) + iden = sympify(matrix * Dagger(matrix)) - if iden != sp.eye(matrix_dim): + if iden != eye(matrix_dim): # There may be a case when there is still a parameter and # we are not quite sure if this is unitary or not. # But just in case we throw exception :) - raise ExtensionError("Input matrix is not unitary.") + raise ExtensionError("Input matrix is not unitary.") from ex # Check input is N-qubit matrix input_dim, output_dim = data.shape diff --git a/qiskit/test/base.py b/qiskit/test/base.py index 7cbd80d73431..e6814a89d1e7 100644 --- a/qiskit/test/base.py +++ b/qiskit/test/base.py @@ -62,6 +62,7 @@ class BaseTestCase(testtools.TestCase): assertRaises = unittest.TestCase.assertRaises assertEqual = unittest.TestCase.assertEqual + else: class BaseTestCase(unittest.TestCase): From 55062005db150653131c9a38ff1400666e606570 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Thu, 16 Dec 2021 09:39:09 -0400 Subject: [PATCH 46/84] added unit test test_unbound_parameters_in_rzx_templates to confirm template optimization handles template optimization with unbound parameters correctly --- .../python/transpiler/test_template_matching.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index 46c684bde00a..c313d11f0e1e 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -24,6 +24,7 @@ from qiskit.converters.circuit_to_dagdependency import circuit_to_dagdependency from qiskit.transpiler import PassManager from qiskit.transpiler.passes import TemplateOptimization +from qiskit.transpiler.passes.calibration.rzx_templates import rzx_templates from qiskit.test import QiskitTestCase from qiskit.transpiler.exceptions import TranspilerError @@ -355,6 +356,22 @@ def template(): self.assertEqual(circuit_out.count_ops().get("cx", 0), 0) + def test_unbound_parameters_in_rzx_template(self): + """ + Test that rzx template ('zz2') functions correctly for a simple + circuit with an unbound ParameterExpression. + """ + + phi = Parameter('$\phi$') + circuit_in = QuantumCircuit(2) + circuit_in.cx(0, 1) + circuit_in.p(2*phi, 1) + circuit_in.cx(0, 1) + + pass_ = TemplateOptimization(**rzx_templates(['zz2'])) + circuit_out = PassManager(pass_).run(circuit_in) + + self.assertEqual(circuit_out.count_ops().get('cx', 0), 0) if __name__ == "__main__": unittest.main() From 708bfcccc5f85e2d068e2abe87f76384ab351b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Pracht?= Date: Fri, 17 Dec 2021 16:50:33 +0100 Subject: [PATCH 47/84] Fix unbund parameters test --- qiskit/circuit/parameterexpression.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 2eb57bc4e70d..57c87e197611 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -440,7 +440,7 @@ def __complex__(self): return complex(self._symbol_expr) # TypeError is for sympy, RuntimeError for symengine except (TypeError, RuntimeError) as exc: - raise TypeError( + raise ParameterTypeError( "ParameterExpression with unbound parameters ({}) " "cannot be cast to a complex.".format(self.parameters) ) from exc @@ -450,7 +450,7 @@ def __float__(self): return float(self._symbol_expr) # TypeError is for sympy, RuntimeError for symengine except (TypeError, RuntimeError) as exc: - raise TypeError( + raise ParameterTypeError( "ParameterExpression with unbound parameters ({}) " "cannot be cast to a float.".format(self.parameters) ) from exc From 54403b012111a154087af1f92b789d05f2a35f35 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Mon, 20 Dec 2021 12:02:33 -0400 Subject: [PATCH 48/84] fixed issue with adding calibrations without params --- qiskit/dagcircuit/dagcircuit.py | 9 ++++++--- test/python/transpiler/test_template_matching.py | 11 ++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 0d183291ccd6..dbbdd35f9182 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -215,8 +215,9 @@ def add_calibration(self, gate, qubits, schedule, params=None): Raises: Exception: if the gate is of type string and params is None. """ + # import pdb; pdb.set_trace() if isinstance(gate, Gate): - try: + if gate.params: for param in gate.params: try: gate_has_param_exp = isinstance(param, ParameterExpression) @@ -224,10 +225,12 @@ def add_calibration(self, gate, qubits, schedule, params=None): gate_has_param_exp = False if gate_has_param_exp: - self._calibrations[gate.name][(tuple(qubits), tuple([float(param)]))] = schedule + self._calibrations[gate.name][ + (tuple(qubits), tuple([float(param)])) + ] = schedule else: self._calibrations[gate.name][(tuple(qubits), tuple(param))] = schedule - except: + else: self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule else: self._calibrations[gate][(tuple(qubits), tuple(params or []))] = schedule diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index c313d11f0e1e..123ea188819f 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -353,7 +353,7 @@ def template(): pass_ = TemplateOptimization(template_list=[template()]) circuit_out = PassManager(pass_).run(circuit_in) - + self.assertEqual(circuit_out.count_ops().get("cx", 0), 0) def test_unbound_parameters_in_rzx_template(self): @@ -362,16 +362,17 @@ def test_unbound_parameters_in_rzx_template(self): circuit with an unbound ParameterExpression. """ - phi = Parameter('$\phi$') + phi = Parameter("$\phi$") circuit_in = QuantumCircuit(2) circuit_in.cx(0, 1) - circuit_in.p(2*phi, 1) + circuit_in.p(2 * phi, 1) circuit_in.cx(0, 1) - pass_ = TemplateOptimization(**rzx_templates(['zz2'])) + pass_ = TemplateOptimization(**rzx_templates(["zz2"])) circuit_out = PassManager(pass_).run(circuit_in) - self.assertEqual(circuit_out.count_ops().get('cx', 0), 0) + self.assertEqual(circuit_out.count_ops().get("cx", 0), 0) + if __name__ == "__main__": unittest.main() From 9355893e951c3757ee4d53faf2b6c49a30244a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Pracht?= Date: Mon, 20 Dec 2021 19:38:41 +0100 Subject: [PATCH 49/84] Add real=True to symbols --- .../optimization/template_matching/template_substitution.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index d6fed977e3b3..801bae565700 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -505,11 +505,11 @@ def _attempt_bind(self, template_sublist, circuit_sublist): equations.append(sym.Eq(template_params.get_sympy_expr(), circ_param_sym)) for param in template_params.parameters: - temp_symbols[param] = sym.Symbol(str(param)) + temp_symbols[param] = sym.Symbol(str(param), real=True) if isinstance(circuit_params[t_idx], ParameterExpression): for param in circuit_params[t_idx].parameters: - circ_dict[param] = sym.Symbol(str(param)) + circ_dict[param] = sym.Symbol(str(param), real=True) if not temp_symbols: return template_dag_dep From 7e9f8d9cf21c281e5846481b437540e02f1fd765 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Mon, 20 Dec 2021 14:41:09 -0400 Subject: [PATCH 50/84] fixed linting issue --- test/python/transpiler/test_template_matching.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index 123ea188819f..1e1786919892 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -353,7 +353,7 @@ def template(): pass_ = TemplateOptimization(template_list=[template()]) circuit_out = PassManager(pass_).run(circuit_in) - + self.assertEqual(circuit_out.count_ops().get("cx", 0), 0) def test_unbound_parameters_in_rzx_template(self): @@ -362,7 +362,7 @@ def test_unbound_parameters_in_rzx_template(self): circuit with an unbound ParameterExpression. """ - phi = Parameter("$\phi$") + phi = Parameter("$\\phi$") circuit_in = QuantumCircuit(2) circuit_in.cx(0, 1) circuit_in.p(2 * phi, 1) From 0a179b62563c21391981013497dac3bf2d6b0299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Pracht?= Date: Mon, 20 Dec 2021 20:38:53 +0100 Subject: [PATCH 51/84] Fix for symengine --- qiskit/extensions/unitary.py | 31 ++++++++++++------- .../template_substitution.py | 16 ++++++++-- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index 520414517e71..b4e352851d2e 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -32,6 +32,13 @@ from qiskit.extensions.exceptions import ExtensionError from qiskit.circuit.parameterexpression import ParameterTypeError, ParameterExpression +try: + import symengine + + HAS_SYMENGINE = True +except ImportError: + HAS_SYMENGINE = False + _DECOMPOSER1Q = OneQubitEulerDecomposer("U3") @@ -85,17 +92,19 @@ def __init__(self, data, label=None): if not is_unitary_matrix(data): raise ExtensionError("Input matrix is not unitary.") except ParameterTypeError as ex: - from sympy import eye, sympify - from sympy.physics.quantum import Dagger - - matrix, matrix_dim = to_sympy_matrix(data) - iden = sympify(matrix * Dagger(matrix)) - - if iden != eye(matrix_dim): - # There may be a case when there is still a parameter and - # we are not quite sure if this is unitary or not. - # But just in case we throw exception :) - raise ExtensionError("Input matrix is not unitary.") from ex + # we can check unitary only for sympy + if not HAS_SYMENGINE: + from sympy import eye, sympify + from sympy.physics.quantum import Dagger + + matrix, matrix_dim = to_sympy_matrix(data) + iden = sympify(matrix * Dagger(matrix)) + + if iden != eye(matrix_dim): + # There may be a case when there is still a parameter and + # we are not quite sure if this is unitary or not. + # But just in case we throw exception :) + raise ExtensionError("Input matrix is not unitary.") from ex # Check input is N-qubit matrix input_dim, output_dim = data.shape diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 801bae565700..4df86edc3210 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -21,6 +21,12 @@ from qiskit.dagcircuit.dagdependency import DAGDependency from qiskit.converters.dagdependency_to_dag import dagdependency_to_dag +try: + import symengine + + HAS_SYMENGINE = True +except ImportError: + HAS_SYMENGINE = False class SubstitutionConfig: """ @@ -505,11 +511,17 @@ def _attempt_bind(self, template_sublist, circuit_sublist): equations.append(sym.Eq(template_params.get_sympy_expr(), circ_param_sym)) for param in template_params.parameters: - temp_symbols[param] = sym.Symbol(str(param), real=True) + if not HAS_SYMENGINE: + temp_symbols[param] = sym.Symbol(str(param), real=True) + else: + temp_symbols[param] = sym.Symbol(str(param)) if isinstance(circuit_params[t_idx], ParameterExpression): for param in circuit_params[t_idx].parameters: - circ_dict[param] = sym.Symbol(str(param), real=True) + if not HAS_SYMENGINE: + circ_dict[param] = sym.Symbol(str(param), real=True) + else: + circ_dict[param] = sym.Symbol(str(param)) if not temp_symbols: return template_dag_dep From 5f860494ae5162a4674d3dad131c327c77617cc0 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Mon, 20 Dec 2021 15:56:44 -0400 Subject: [PATCH 52/84] simplified the parameter handling for adding calibrations to gates --- qiskit/dagcircuit/dagcircuit.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index dbbdd35f9182..7580a0b5cd44 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -215,23 +215,9 @@ def add_calibration(self, gate, qubits, schedule, params=None): Raises: Exception: if the gate is of type string and params is None. """ - # import pdb; pdb.set_trace() + if isinstance(gate, Gate): - if gate.params: - for param in gate.params: - try: - gate_has_param_exp = isinstance(param, ParameterExpression) - except IndexError: - gate_has_param_exp = False - - if gate_has_param_exp: - self._calibrations[gate.name][ - (tuple(qubits), tuple([float(param)])) - ] = schedule - else: - self._calibrations[gate.name][(tuple(qubits), tuple(param))] = schedule - else: - self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule + self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule else: self._calibrations[gate][(tuple(qubits), tuple(params or []))] = schedule From 680ec53cdaa8ca041494213adad93f6d1c004e8f Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Mon, 20 Dec 2021 17:30:44 -0400 Subject: [PATCH 53/84] added a check for unitary on an arbitrary float in the case symengine is imported (and fixed a couple minor bugs) --- qiskit/extensions/unitary.py | 16 ++++++++++++++++ .../template_matching/template_substitution.py | 5 +++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index b4e352851d2e..cc065cce4c93 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -106,6 +106,22 @@ def __init__(self, data, label=None): # But just in case we throw exception :) raise ExtensionError("Input matrix is not unitary.") from ex + else: + # Bind to test value and convert to numpy array in case not already an array + for ii in range(numpy.shape(data)[0]): + for jj in range(numpy.shape(data)[1]): + if isinstance(data[ii][jj], ParameterExpression): + for param in data[ii][jj].parameters: + data[ii][jj] = data[ii][jj].bind( + {param: float(symengine.conjugate(0.42))} + ) + + data = numpy.array(data, dtype=complex) + + # Check input is unitary + if not is_unitary_matrix(data): + raise ExtensionError("Input matrix is not unitary.") from ex + # Check input is N-qubit matrix input_dim, output_dim = data.shape num_qubits = int(numpy.log2(input_dim)) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 4df86edc3210..4c23271214a9 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -28,6 +28,7 @@ except ImportError: HAS_SYMENGINE = False + class SubstitutionConfig: """ Class to store the configuration of a given match substitution, which circuit @@ -514,14 +515,14 @@ def _attempt_bind(self, template_sublist, circuit_sublist): if not HAS_SYMENGINE: temp_symbols[param] = sym.Symbol(str(param), real=True) else: - temp_symbols[param] = sym.Symbol(str(param)) + temp_symbols[param] = symengine.Symbol(str(param)) if isinstance(circuit_params[t_idx], ParameterExpression): for param in circuit_params[t_idx].parameters: if not HAS_SYMENGINE: circ_dict[param] = sym.Symbol(str(param), real=True) else: - circ_dict[param] = sym.Symbol(str(param)) + circ_dict[param] = symengine.Symbol(str(param)) if not temp_symbols: return template_dag_dep From f276cd71f874a8d2ad8c49618f7a908c444fa7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Pracht?= Date: Wed, 29 Dec 2021 09:49:51 +0100 Subject: [PATCH 54/84] Parammeter can be complex --- qiskit/circuit/parameter.py | 7 +-- qiskit/extensions/unitary.py | 48 ++++++++----------- .../template_substitution.py | 4 +- 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/qiskit/circuit/parameter.py b/qiskit/circuit/parameter.py index 98abd5d9c59e..3f2fe4b25a7a 100644 --- a/qiskit/circuit/parameter.py +++ b/qiskit/circuit/parameter.py @@ -58,9 +58,7 @@ def __init__(self, name: str): self._name = name if not HAS_SYMENGINE: from sympy import Symbol - - # Parameter can only be real - symbol = Symbol(name, real=True) + symbol = Symbol(name) else: symbol = symengine.Symbol(name) super().__init__(symbol_map={self: symbol}, expr=symbol) @@ -104,8 +102,7 @@ def __setstate__(self, state): self._name = state["name"] if not HAS_SYMENGINE: from sympy import Symbol - - symbol = Symbol(self._name, real=True) + symbol = Symbol(self._name) else: symbol = symengine.Symbol(self._name) super().__init__(symbol_map={self: symbol}, expr=symbol) diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index cc065cce4c93..bf7f6f2deabc 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -92,35 +92,29 @@ def __init__(self, data, label=None): if not is_unitary_matrix(data): raise ExtensionError("Input matrix is not unitary.") except ParameterTypeError as ex: - # we can check unitary only for sympy if not HAS_SYMENGINE: - from sympy import eye, sympify - from sympy.physics.quantum import Dagger - - matrix, matrix_dim = to_sympy_matrix(data) - iden = sympify(matrix * Dagger(matrix)) - - if iden != eye(matrix_dim): - # There may be a case when there is still a parameter and - # we are not quite sure if this is unitary or not. - # But just in case we throw exception :) - raise ExtensionError("Input matrix is not unitary.") from ex - + from sympy import conjugate + conj = lambda x: conjugate(x) else: - # Bind to test value and convert to numpy array in case not already an array - for ii in range(numpy.shape(data)[0]): - for jj in range(numpy.shape(data)[1]): - if isinstance(data[ii][jj], ParameterExpression): - for param in data[ii][jj].parameters: - data[ii][jj] = data[ii][jj].bind( - {param: float(symengine.conjugate(0.42))} - ) - - data = numpy.array(data, dtype=complex) - - # Check input is unitary - if not is_unitary_matrix(data): - raise ExtensionError("Input matrix is not unitary.") from ex + conj = lambda x: symengine.conjugate(x) + + # Bind to test value and convert to numpy array in case not already an array + for ii in range(numpy.shape(data)[0]): + for jj in range(numpy.shape(data)[1]): + if isinstance(data[ii][jj], ParameterExpression): + for param in data[ii][jj].parameters: + if not HAS_SYMENGINE: + from sympy import eye, sympify + from sympy.physics.quantum import Dagger + + data[ii][jj] = data[ii][jj].bind( + {param: float(conj(0.42))} + ) + data = numpy.array(data, dtype=complex) + + # Check input is unitary + if not is_unitary_matrix(data): + raise ExtensionError("Input matrix is not unitary.") from ex # Check input is N-qubit matrix input_dim, output_dim = data.shape diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 4c23271214a9..d67d25feacdb 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -513,14 +513,14 @@ def _attempt_bind(self, template_sublist, circuit_sublist): for param in template_params.parameters: if not HAS_SYMENGINE: - temp_symbols[param] = sym.Symbol(str(param), real=True) + temp_symbols[param] = sym.Symbol(str(param)) else: temp_symbols[param] = symengine.Symbol(str(param)) if isinstance(circuit_params[t_idx], ParameterExpression): for param in circuit_params[t_idx].parameters: if not HAS_SYMENGINE: - circ_dict[param] = sym.Symbol(str(param), real=True) + circ_dict[param] = sym.Symbol(str(param)) else: circ_dict[param] = symengine.Symbol(str(param)) From 055832d29a11ccd477bf7cf2404d18b8de537fe7 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Tue, 4 Jan 2022 13:21:56 -0400 Subject: [PATCH 55/84] fixed tox/lint issues --- qiskit/circuit/parameter.py | 2 ++ qiskit/extensions/unitary.py | 21 +-------------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/qiskit/circuit/parameter.py b/qiskit/circuit/parameter.py index 04ed814f249d..0daab9fb9f17 100644 --- a/qiskit/circuit/parameter.py +++ b/qiskit/circuit/parameter.py @@ -83,6 +83,7 @@ def __init__(self, name: str): self._name = name if not HAS_SYMENGINE: from sympy import Symbol + symbol = Symbol(name) else: symbol = symengine.Symbol(name) @@ -127,6 +128,7 @@ def __setstate__(self, state): self._name = state["name"] if not HAS_SYMENGINE: from sympy import Symbol + symbol = Symbol(self._name) else: symbol = symengine.Symbol(self._name) diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index bf7f6f2deabc..e52e62550a75 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -32,13 +32,6 @@ from qiskit.extensions.exceptions import ExtensionError from qiskit.circuit.parameterexpression import ParameterTypeError, ParameterExpression -try: - import symengine - - HAS_SYMENGINE = True -except ImportError: - HAS_SYMENGINE = False - _DECOMPOSER1Q = OneQubitEulerDecomposer("U3") @@ -92,24 +85,12 @@ def __init__(self, data, label=None): if not is_unitary_matrix(data): raise ExtensionError("Input matrix is not unitary.") except ParameterTypeError as ex: - if not HAS_SYMENGINE: - from sympy import conjugate - conj = lambda x: conjugate(x) - else: - conj = lambda x: symengine.conjugate(x) - # Bind to test value and convert to numpy array in case not already an array for ii in range(numpy.shape(data)[0]): for jj in range(numpy.shape(data)[1]): if isinstance(data[ii][jj], ParameterExpression): for param in data[ii][jj].parameters: - if not HAS_SYMENGINE: - from sympy import eye, sympify - from sympy.physics.quantum import Dagger - - data[ii][jj] = data[ii][jj].bind( - {param: float(conj(0.42))} - ) + data[ii][jj] = data[ii][jj].bind({param: float(0.42)}) data = numpy.array(data, dtype=complex) # Check input is unitary From 2504c2e0b8afda694ff8ab992c02a1b8b983f349 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 5 Jan 2022 18:26:18 -0400 Subject: [PATCH 56/84] removed one more imposition of real parameters --- qiskit/circuit/parameterexpression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 57c87e197611..09b76016574a 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -181,7 +181,7 @@ def subs(self, parameter_map: Dict) -> "ParameterExpression": else: from sympy import Symbol - new_parameter_symbols = {p: Symbol(p.name, real=True) for p in inbound_parameters} + new_parameter_symbols = {p: Symbol(p.name) for p in inbound_parameters} # Include existing parameters in self not set to be replaced. new_parameter_symbols.update( From 0f1cf5d56142403570a0442e39916766b13e3921 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 5 Jan 2022 19:20:51 -0400 Subject: [PATCH 57/84] one last linting issue --- qiskit/test/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/test/base.py b/qiskit/test/base.py index e6814a89d1e7..7cbd80d73431 100644 --- a/qiskit/test/base.py +++ b/qiskit/test/base.py @@ -62,7 +62,6 @@ class BaseTestCase(testtools.TestCase): assertRaises = unittest.TestCase.assertRaises assertEqual = unittest.TestCase.assertEqual - else: class BaseTestCase(unittest.TestCase): From 9a94bb29362bdca99b30029322b15ddc24e15e45 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 26 Jan 2022 17:15:07 -0400 Subject: [PATCH 58/84] modified release notes --- ...emplate-substitution-a1379cdbfcc10b5c.yaml | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml index 7fe05d8122f5..ab2ac2e4a226 100644 --- a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml +++ b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml @@ -1,23 +1,13 @@ --- features: - | - The Parameter class is now allowed in template optimization if the - parameterized gates are RZXGates and RXGates. It has been removed from - the _remove_impossible() method for scenarios that are incompatible - with template optimization. Inversion of gates is currently only - implemented for the RXGate(). -issues: - - | - Currently the only gate that can be inverted (as done in the template - optimization algorithm) with a Parameter is the RXGate(). -other: - - | - An illustrative example of this PR is modeled after the addition of - calibrations for template optimization #5752: + The Parameter class is now allowed in the template optimization + transpiler pass. An illustrative example of using Parameters is + the following: .. code-block:: - from qiskit import QuantumCircuit, transpile, schedule, IBMQ + from qiskit import QuantumCircuit, transpile, schedule from qiskit.circuit import Parameter from qiskit.transpiler import PassManager @@ -26,9 +16,8 @@ other: # New contributions to the template optimization from qiskit.transpiler.passes.scheduling import RZXCalibrationBuilder, rzx_templates - IBMQ.load_account() - provider = IBMQ.get_provider(...) - backend = provider.get_backend('ibmq_casablanca') + from qiskit.test.mock import FakeCasablanca + backend = FakeCasablanca() phi = Parameter('φ') @@ -44,7 +33,7 @@ other: print('ZX based circuit:') print(qc_cz) - # Add the calibratiocns + # Add the calibrations pass_ = RZXCalibrationBuilder(backend) cal_qc = PassManager(pass_).run(qc_cz.bind_parameters({phi: 0.12})) From 48173c149aa9c09a1a90c7c41f50c37cc08bca27 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Thu, 27 Jan 2022 10:33:40 -0400 Subject: [PATCH 59/84] fixed some transpiler library imports that were out of date --- ...dd-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml index ab2ac2e4a226..a5a920fc3da2 100644 --- a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml +++ b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml @@ -28,7 +28,7 @@ features: print('Original circuit:') print(qc) - pass_ = TemplateOptimization(**rzx_templates(['zz2'])) + pass_ = TemplateOptimization(**rzx_templates.rzx_templates(['zz2'])) qc_cz = PassManager(pass_).run(qc) print('ZX based circuit:') print(qc_cz) From 24097b5710291a50f568e94f000d8d5eb0c3bc2e Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Thu, 27 Jan 2022 11:30:43 -0400 Subject: [PATCH 60/84] added sphinx referencing to release notes and print statement for the case of testing unbound parameters when creating a unitary gate --- qiskit/extensions/unitary.py | 14 +++----------- ...-to-template-substitution-a1379cdbfcc10b5c.yaml | 11 ++++++----- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index e52e62550a75..553e0018a014 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -65,6 +65,7 @@ def __init__(self, data, label=None): Raises: ExtensionError: if input data is not an N-qubit unitary operator. + ParameterTypeError: if parameter is unbound so that type cannot be determined. """ if hasattr(data, "to_matrix"): # If input is Gate subclass or some other class object that has @@ -85,17 +86,8 @@ def __init__(self, data, label=None): if not is_unitary_matrix(data): raise ExtensionError("Input matrix is not unitary.") except ParameterTypeError as ex: - # Bind to test value and convert to numpy array in case not already an array - for ii in range(numpy.shape(data)[0]): - for jj in range(numpy.shape(data)[1]): - if isinstance(data[ii][jj], ParameterExpression): - for param in data[ii][jj].parameters: - data[ii][jj] = data[ii][jj].bind({param: float(0.42)}) - data = numpy.array(data, dtype=complex) - - # Check input is unitary - if not is_unitary_matrix(data): - raise ExtensionError("Input matrix is not unitary.") from ex + print("Type of unbound Parameter cannot be determined to ensure unitarity.") + pass # Check input is N-qubit matrix input_dim, output_dim = data.shape diff --git a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml index a5a920fc3da2..bcc1da8febb0 100644 --- a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml +++ b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml @@ -1,9 +1,9 @@ --- features: - | - The Parameter class is now allowed in the template optimization - transpiler pass. An illustrative example of using Parameters is - the following: + The :class:`.ParameterExpression` class is now allowed in the + template optimization transpiler pass. An illustrative example + of using :class:`.Parameter`\s is the following: .. code-block:: @@ -73,5 +73,6 @@ features: Duration of standard with two CNOT gates: 6848 - Note that the Parameters/ParameterExpressions must be bound before the - RZXCalibrationBuilder can define calibrations for the RZXGates. + Note that the :class:`.Parameter`\s/:class:`.ParameterExpression`\s + must be bound before the :class:`.RZXCalibrationBuilder` can define + calibrations for the :class:`.RZXGate`\ s. From 2f0203c49a2fb92ae019480729456f75b9831762 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Thu, 27 Jan 2022 18:07:32 -0400 Subject: [PATCH 61/84] fixed some tox/lint issues --- qiskit/extensions/unitary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index 553e0018a014..60874f97d014 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -85,7 +85,7 @@ def __init__(self, data, label=None): # Check input is unitary if not is_unitary_matrix(data): raise ExtensionError("Input matrix is not unitary.") - except ParameterTypeError as ex: + except ParameterTypeError: print("Type of unbound Parameter cannot be determined to ensure unitarity.") pass From 591763ac4091e24b4c7e9f2b9c658ec3ab9a5cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Pracht?= Date: Fri, 28 Jan 2022 19:50:19 +0100 Subject: [PATCH 62/84] Fix review issues --- qiskit/circuit/exceptions.py | 6 +++ qiskit/circuit/parameterexpression.py | 10 +---- qiskit/extensions/unitary.py | 21 +---------- .../transpiler/passes/calibration/builders.py | 5 ++- .../template_substitution.py | 37 ++++++------------- 5 files changed, 25 insertions(+), 54 deletions(-) diff --git a/qiskit/circuit/exceptions.py b/qiskit/circuit/exceptions.py index b3a06ede2380..9a62385b7772 100644 --- a/qiskit/circuit/exceptions.py +++ b/qiskit/circuit/exceptions.py @@ -19,3 +19,9 @@ class CircuitError(QiskitError): """Base class for errors raised while processing a circuit.""" pass + + +class ParameterTypeError(TypeError): + """The type error throw by parameter if we try to cast to number unbind parameter.""" + + pass \ No newline at end of file diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 09b76016574a..6d49dc0f5d83 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -19,7 +19,7 @@ import numpy -from qiskit.circuit.exceptions import CircuitError +from qiskit.circuit.exceptions import CircuitError, ParameterTypeError try: import symengine @@ -35,12 +35,6 @@ ParameterValueType = Union["ParameterExpression", float] -class ParameterTypeError(TypeError): - """The type error throw by parameter if we try to cast to number unbind parameter.""" - - pass - - class ParameterExpression: """ParameterExpression class to enable creating expressions of Parameters.""" @@ -538,7 +532,7 @@ def is_real(self): return False return True - def get_sympy_expr(self): + def to_simplify_expression(self): """Return symbolic expression from sympy/symengine""" if HAS_SYMENGINE: return symengine.sympify(self._symbol_expr) diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index 60874f97d014..7b0bea77e789 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -30,29 +30,12 @@ from qiskit.quantum_info.synthesis.one_qubit_decompose import OneQubitEulerDecomposer from qiskit.quantum_info.synthesis.two_qubit_decompose import two_qubit_cnot_decompose from qiskit.extensions.exceptions import ExtensionError -from qiskit.circuit.parameterexpression import ParameterTypeError, ParameterExpression +from qiskit.circuit.parameterexpression import ParameterExpression +from qiskit.circuit.exceptions import ParameterTypeError _DECOMPOSER1Q = OneQubitEulerDecomposer("U3") -def to_sympy_matrix(numpy_matrix): - """Convert (numpy) matrix to sympy matrix.""" - matrix = [] - i = 0 - for row in numpy_matrix: - nrow = [] - i += 1 - for expr in row: - if isinstance(expr, ParameterExpression): - expr = expr.get_sympy_expr() - nrow.append(expr) - matrix.append(nrow) - - from sympy import Matrix - - return Matrix(matrix), i - - class UnitaryGate(Gate): """Class for representing unitary gates""" diff --git a/qiskit/transpiler/passes/calibration/builders.py b/qiskit/transpiler/passes/calibration/builders.py index 6cbf48c92c54..c936a1edaea0 100644 --- a/qiskit/transpiler/passes/calibration/builders.py +++ b/qiskit/transpiler/passes/calibration/builders.py @@ -25,6 +25,7 @@ from qiskit.circuit.library.standard_gates import RZXGate from qiskit.dagcircuit import DAGCircuit from qiskit.exceptions import QiskitError +from qiskit.transpiler.exceptions import TranspilerError from qiskit.pulse import ( Play, Delay, @@ -231,8 +232,8 @@ def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, try: theta = float(node_op.params[0]) except TypeError as ex: - raise QiskitError( - "This transpilation pass requires all Parameters to be bound." + raise TranspilerError( + "This transpilation pass requires all Parameters to be bound and real." ) from ex q1, q2 = qubits[0], qubits[1] diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index d67d25feacdb..12c63c037204 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -21,13 +21,6 @@ from qiskit.dagcircuit.dagdependency import DAGDependency from qiskit.converters.dagdependency_to_dag import dagdependency_to_dag -try: - import symengine - - HAS_SYMENGINE = True -except ImportError: - HAS_SYMENGINE = False - class SubstitutionConfig: """ @@ -503,26 +496,20 @@ def _attempt_bind(self, template_sublist, circuit_sublist): # Create the fake binding dict and check equations, circ_dict, temp_symbols, sol, fake_bind = [], {}, {}, {}, {} - for t_idx, template_params in enumerate(template_params): - if isinstance(template_params, ParameterExpression): - if isinstance(circuit_params[t_idx], ParameterExpression): - circ_param_sym = circuit_params[t_idx].get_sympy_expr() + for circuit_param, template_param in zip(circuit_params, template_params): + if isinstance(template_param, ParameterExpression): + if isinstance(circuit_param, ParameterExpression): + circ_param_sym = circuit_param.to_simplify_expression() else: - circ_param_sym = parse_expr(str(circuit_params[t_idx])) - equations.append(sym.Eq(template_params.get_sympy_expr(), circ_param_sym)) + circ_param_sym = parse_expr(str(circuit_param)) + equations.append(sym.Eq(template_param.to_simplify_expression(), circ_param_sym)) - for param in template_params.parameters: - if not HAS_SYMENGINE: - temp_symbols[param] = sym.Symbol(str(param)) - else: - temp_symbols[param] = symengine.Symbol(str(param)) - - if isinstance(circuit_params[t_idx], ParameterExpression): - for param in circuit_params[t_idx].parameters: - if not HAS_SYMENGINE: - circ_dict[param] = sym.Symbol(str(param)) - else: - circ_dict[param] = symengine.Symbol(str(param)) + for param in template_param.parameters: + temp_symbols[param] = param.to_simplify_expression() + + if isinstance(circuit_param, ParameterExpression): + for param in circuit_param.parameters: + circ_dict[param] = param.to_simplify_expression() if not temp_symbols: return template_dag_dep From c8a08e95befe2ac868e2da0112dd2b8e385bdfff Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Tue, 1 Feb 2022 23:01:04 -0500 Subject: [PATCH 63/84] fixing last tox/lint issues --- qiskit/circuit/exceptions.py | 2 +- qiskit/extensions/unitary.py | 1 - qiskit/transpiler/passes/calibration/builders.py | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/circuit/exceptions.py b/qiskit/circuit/exceptions.py index 9a62385b7772..ab92a65cfe0c 100644 --- a/qiskit/circuit/exceptions.py +++ b/qiskit/circuit/exceptions.py @@ -24,4 +24,4 @@ class CircuitError(QiskitError): class ParameterTypeError(TypeError): """The type error throw by parameter if we try to cast to number unbind parameter.""" - pass \ No newline at end of file + pass diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index 7b0bea77e789..c3ee001d3ac7 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -30,7 +30,6 @@ from qiskit.quantum_info.synthesis.one_qubit_decompose import OneQubitEulerDecomposer from qiskit.quantum_info.synthesis.two_qubit_decompose import two_qubit_cnot_decompose from qiskit.extensions.exceptions import ExtensionError -from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.circuit.exceptions import ParameterTypeError _DECOMPOSER1Q = OneQubitEulerDecomposer("U3") diff --git a/qiskit/transpiler/passes/calibration/builders.py b/qiskit/transpiler/passes/calibration/builders.py index c936a1edaea0..e5202f53cd18 100644 --- a/qiskit/transpiler/passes/calibration/builders.py +++ b/qiskit/transpiler/passes/calibration/builders.py @@ -225,9 +225,10 @@ def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, schedule: The calibration schedule for the RZXGate(theta). Raises: - QiskitError: If all Parameters are not bound, if the control and target + QiskitError: If the control and target qubits cannot be identified, or the backend does not support cx between the qubits. + TranspilerError: If all Parameters are not bound. """ try: theta = float(node_op.params[0]) From 3f740fbe4957573e21d52691a8c2d149d28de3bc Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Fri, 11 Feb 2022 16:11:42 -0500 Subject: [PATCH 64/84] added release notes and fixed tox/lint issues --- .../library/probability_distributions/__init__.py | 11 +++++++++++ qiskit/circuit/parameterexpression.py | 7 +------ 2 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 qiskit/circuit/library/probability_distributions/__init__.py diff --git a/qiskit/circuit/library/probability_distributions/__init__.py b/qiskit/circuit/library/probability_distributions/__init__.py new file mode 100644 index 000000000000..9a29547928ea --- /dev/null +++ b/qiskit/circuit/library/probability_distributions/__init__.py @@ -0,0 +1,11 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 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. diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 086be5b81307..f521714094da 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -19,8 +19,8 @@ import numpy -<<<<<<< HEAD from qiskit.circuit.exceptions import CircuitError, ParameterTypeError +from qiskit.utils import optionals as _optionals try: import symengine @@ -28,11 +28,6 @@ HAS_SYMENGINE = True except ImportError: HAS_SYMENGINE = False -======= -from qiskit.circuit.exceptions import CircuitError -from qiskit.utils import optionals as _optionals ->>>>>>> main - # This type is redefined at the bottom to insert the full reference to "ParameterExpression", so it # can safely be used by runtime type-checkers like Sphinx. Mypy does not need this because it From 69b8472f6f82e5e85bfa967fa52b2ef84dc64498 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 16 Feb 2022 21:26:29 -0500 Subject: [PATCH 65/84] added method in template_substitution to compare the number of parameters in circuit with the that of the template that would potentially replace it --- qiskit/extensions/unitary.py | 18 ++----- .../template_substitution.py | 23 ++++++-- .../transpiler/test_template_matching.py | 53 +++++++++---------- 3 files changed, 50 insertions(+), 44 deletions(-) diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index e6ccc9b71439..b9b0b46ca38a 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -30,7 +30,6 @@ from qiskit.quantum_info.synthesis.one_qubit_decompose import OneQubitEulerDecomposer from qiskit.quantum_info.synthesis.two_qubit_decompose import two_qubit_cnot_decompose from qiskit.extensions.exceptions import ExtensionError -from qiskit.circuit.exceptions import ParameterTypeError _DECOMPOSER1Q = OneQubitEulerDecomposer("U3") @@ -47,7 +46,6 @@ def __init__(self, data, label=None): Raises: ExtensionError: if input data is not an N-qubit unitary operator. - ParameterTypeError: if parameter is unbound so that type cannot be determined. """ if hasattr(data, "to_matrix"): # If input is Gate subclass or some other class object that has @@ -59,17 +57,11 @@ def __init__(self, data, label=None): # numpy matrix from `Operator.data`. data = data.to_operator().data - # Check if there is any unbound parameter in array - try: - # Convert to numpy array in case not already an array - data = numpy.array(data, dtype=complex) - - # Check input is unitary - if not is_unitary_matrix(data): - raise ExtensionError("Input matrix is not unitary.") - except ParameterTypeError: - print("Type of unbound Parameter cannot be determined to ensure unitarity.") - pass + # Convert to numpy array in case not already an array + data = numpy.array(data, dtype=complex) + # Check input is unitary + if not is_unitary_matrix(data): + raise ExtensionError("Input matrix is not unitary.") # Check input is N-qubit matrix input_dim, output_dim = data.shape diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 11fe170db773..39eb7432aa87 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -175,7 +175,6 @@ def _rules(self, circuit_sublist, template_sublist, template_complement): Returns: bool: True if the match respects the given rule for replacement, False otherwise. """ - if self._quantum_cost(template_sublist, template_complement): for elem in circuit_sublist: for config in self.substitution_list: @@ -319,8 +318,7 @@ def _substitution(self): # Fake bind any parameters in the template template = self._attempt_bind(template_sublist, circuit_sublist) - - if template is None: + if template is None or self._incr_num_parameters(): continue template_list = range(0, self.template_dag_dep.size()) @@ -548,3 +546,22 @@ def _attempt_bind(self, template_sublist, circuit_sublist): node.op.params = bound_params return template_dag_dep + + def _incr_num_parameters(self): + """ + Checks if template substitution would increase the number of + parameters in the circuit. + """ + template_params = set() + for param_list in (node.op.params for node in self.template_dag_dep.get_nodes()): + for param_exp in param_list: + if isinstance(param_exp, ParameterExpression): + template_params.update(param_exp.parameters) + + circuit_params = set() + for param_list in (node.op.params for node in self.circuit_dag_dep.get_nodes()): + for param_exp in param_list: + if isinstance(param_exp, ParameterExpression): + circuit_params.update(param_exp.parameters) + + return len(template_params) > len(circuit_params) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index 1e1786919892..2ab6d2659afe 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -315,46 +315,34 @@ def count_cx(qc): self.assertEqual(count_cx(circuit_out), 2) # One match => two CX gates. np.testing.assert_almost_equal(Operator(circuit_in).data, Operator(circuit_out).data) - def test_unbound_parameters(self): + def test_optimizer_does_not_replace_unbound_partial_match(self): """ Test that partial matches with parameters will not raise errors. This tests that if parameters are still in the temporary template after _attempt_bind then they will not be used. """ - class PhaseSwap(Gate): - """CZ gates used for the test.""" - - def __init__(self, num_qubits, params): - super().__init__("p", num_qubits, params) - - def inverse(self): - inverse = UnitaryGate( - np.diag( - [1.0, 1.0, np.exp(-1.0j * self.params[0]), np.exp(-1.0j * self.params[0])] - ) - ) - inverse.name = "p" - return inverse - - def template(): - beta = Parameter("β") - qc = QuantumCircuit(2) - qc.cx(1, 0) - qc.cx(1, 0) - qc.p(beta, 1) - qc.append(PhaseSwap(2, [beta]), [0, 1]) - - return qc + beta = Parameter("β") + template = QuantumCircuit(2) + template.cx(1, 0) + template.cx(1, 0) + template.p(beta, 1) + template.cu(0, 0, 0, -beta, 0, 1) circuit_in = QuantumCircuit(2) circuit_in.cx(1, 0) circuit_in.cx(1, 0) + pass_ = TemplateOptimization( + template_list=[template], + user_cost_dict={"cx": 6, "p": 0, "cu": 8}, + ) - pass_ = TemplateOptimization(template_list=[template()]) circuit_out = PassManager(pass_).run(circuit_in) - self.assertEqual(circuit_out.count_ops().get("cx", 0), 0) + # The template optimisation should not have replaced anything, because + # that would require it to leave dummy parameters in place without + # binding them. + self.assertEqual(circuit_in, circuit_out) def test_unbound_parameters_in_rzx_template(self): """ @@ -371,7 +359,16 @@ def test_unbound_parameters_in_rzx_template(self): pass_ = TemplateOptimization(**rzx_templates(["zz2"])) circuit_out = PassManager(pass_).run(circuit_in) - self.assertEqual(circuit_out.count_ops().get("cx", 0), 0) + # this is FALSE if template optimization works + circuit_equiv = circuit_in == circuit_out + + # however this is TRUE if the operators are the same + phi_set = 0.42 + op_equiv = Operator(circuit_in.bind_parameters({phi: phi_set})).equiv( + circuit_out.bind_parameters({phi: phi_set}) + ) + + self.assertEqual(op_equiv and not circuit_equiv, True) if __name__ == "__main__": From e09b1e3f0fb0847cb98a3e70bbabb18554c357a4 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Wed, 16 Feb 2022 23:20:24 -0500 Subject: [PATCH 66/84] fixing up some template matching unit tests --- qiskit/circuit/parameterexpression.py | 27 +------- .../template_substitution.py | 6 +- .../transpiler/test_template_matching.py | 64 ++++++++++--------- 3 files changed, 40 insertions(+), 57 deletions(-) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index f521714094da..4fef9c89cca0 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -25,9 +25,9 @@ try: import symengine - HAS_SYMENGINE = True + _optionals.HAS_SYMENGINE = True except ImportError: - HAS_SYMENGINE = False + _optionals.HAS_SYMENGINE = False # This type is redefined at the bottom to insert the full reference to "ParameterExpression", so it # can safely be used by runtime type-checkers like Sphinx. Mypy does not need this because it @@ -72,8 +72,6 @@ def _names(self) -> Dict: def conjugate(self) -> "ParameterExpression": """Return the conjugate.""" if _optionals.HAS_SYMENGINE: - import symengine - conjugated = ParameterExpression( self._parameter_symbols, symengine.conjugate(self._symbol_expr) ) @@ -173,8 +171,6 @@ def subs(self, parameter_map: Dict) -> "ParameterExpression": self._raise_if_passed_unknown_parameters(parameter_map.keys()) self._raise_if_parameter_names_conflict(inbound_names, parameter_map.keys()) if _optionals.HAS_SYMENGINE: - import symengine - new_parameter_symbols = {p: symengine.Symbol(p.name) for p in inbound_parameters} else: from sympy import Symbol @@ -297,8 +293,6 @@ def gradient(self, param) -> Union["ParameterExpression", complex]: # Compute the gradient of the parameter expression w.r.t. param key = self._parameter_symbols[param] if _optionals.HAS_SYMENGINE: - import symengine - expr_grad = symengine.Derivative(self._symbol_expr, key) else: # TODO enable nth derivative @@ -358,8 +352,6 @@ def _call(self, ufunc): def sin(self): """Sine of a ParameterExpression""" if _optionals.HAS_SYMENGINE: - import symengine - return self._call(symengine.sin) else: from sympy import sin as _sin @@ -369,8 +361,6 @@ def sin(self): def cos(self): """Cosine of a ParameterExpression""" if _optionals.HAS_SYMENGINE: - import symengine - return self._call(symengine.cos) else: from sympy import cos as _cos @@ -380,8 +370,6 @@ def cos(self): def tan(self): """Tangent of a ParameterExpression""" if _optionals.HAS_SYMENGINE: - import symengine - return self._call(symengine.tan) else: from sympy import tan as _tan @@ -391,8 +379,6 @@ def tan(self): def arcsin(self): """Arcsin of a ParameterExpression""" if _optionals.HAS_SYMENGINE: - import symengine - return self._call(symengine.asin) else: from sympy import asin as _asin @@ -402,8 +388,6 @@ def arcsin(self): def arccos(self): """Arccos of a ParameterExpression""" if _optionals.HAS_SYMENGINE: - import symengine - return self._call(symengine.acos) else: from sympy import acos as _acos @@ -413,8 +397,6 @@ def arccos(self): def arctan(self): """Arctan of a ParameterExpression""" if _optionals.HAS_SYMENGINE: - import symengine - return self._call(symengine.atan) else: from sympy import atan as _atan @@ -424,7 +406,6 @@ def arctan(self): def exp(self): """Exponential of a ParameterExpression""" if _optionals.HAS_SYMENGINE: - import symengine return self._call(symengine.exp) else: @@ -435,7 +416,6 @@ def exp(self): def log(self): """Logarithm of a ParameterExpression""" if _optionals.HAS_SYMENGINE: - import symengine return self._call(symengine.log) else: @@ -529,7 +509,6 @@ def __getstate__(self): def __setstate__(self, state): if state["type"] == "symengine": - import symengine self._symbol_expr = symengine.sympify(state["expr"]) self._parameter_symbols = {k: symengine.sympify(v) for k, v in state["symbols"].items()} @@ -558,7 +537,7 @@ def is_real(self): def to_simplify_expression(self): """Return symbolic expression from sympy/symengine""" - if HAS_SYMENGINE: + if _optionals.HAS_SYMENGINE: return symengine.sympify(self._symbol_expr) else: from sympy import sympify diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 39eb7432aa87..3aa96bda2ba7 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -318,7 +318,7 @@ def _substitution(self): # Fake bind any parameters in the template template = self._attempt_bind(template_sublist, circuit_sublist) - if template is None or self._incr_num_parameters(): + if template is None or self._incr_num_parameters(template): continue template_list = range(0, self.template_dag_dep.size()) @@ -547,13 +547,13 @@ def _attempt_bind(self, template_sublist, circuit_sublist): return template_dag_dep - def _incr_num_parameters(self): + def _incr_num_parameters(self, template): """ Checks if template substitution would increase the number of parameters in the circuit. """ template_params = set() - for param_list in (node.op.params for node in self.template_dag_dep.get_nodes()): + for param_list in (node.op.params for node in template.get_nodes()): for param_exp in param_list: if isinstance(param_exp, ParameterExpression): template_params.update(param_exp.parameters) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index 2ab6d2659afe..a53a00262838 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -19,6 +19,7 @@ from qiskit.circuit import Parameter, Gate from qiskit.extensions import UnitaryGate from qiskit.quantum_info import Operator +from qiskit.circuit.library import CUGate from qiskit.circuit.library.templates import template_nct_2a_2, template_nct_5a_3 from qiskit.converters.circuit_to_dag import circuit_to_dag from qiskit.converters.circuit_to_dagdependency import circuit_to_dagdependency @@ -250,28 +251,27 @@ def test_parametric_template(self): └──────┘ └───┘└──────┘└───┘ """ - class CZp(Gate): - """CZ gates used for the test.""" + #class CZp(Gate): + # """CZ gates used for the test.""" + # + # def __init__(self, num_qubits, params): + # super().__init__("cz", num_qubits, params) + # + # def inverse(self): + # #inverse = UnitaryGate(np.diag([1.0, 1.0, 1.0, np.exp(-2.0j * self.params[0])])) + # inverse = CUGate(0, 0, 0, -2.0j * self.params[0]) + # inverse.name = "icz" + # return inverse - def __init__(self, num_qubits, params): - super().__init__("cz", num_qubits, params) - def inverse(self): - inverse = UnitaryGate(np.diag([1.0, 1.0, 1.0, np.exp(-2.0j * self.params[0])])) - inverse.name = "icz" - return inverse - - def template_czp2(): - beta = Parameter("β") - qc = QuantumCircuit(2) - qc.p(-beta, 0) - qc.p(-beta, 1) - qc.cx(0, 1) - qc.p(beta, 1) - qc.cx(0, 1) - qc.append(CZp(2, [beta]), [0, 1]) - - return qc + beta = Parameter("β") + template = QuantumCircuit(2) + template.p(-beta, 0) + template.p(-beta, 1) + template.cx(0, 1) + template.p(beta, 1) + template.cx(0, 1) + template.cu(0, 0, 0, beta, 0, 1) def count_cx(qc): """Counts the number of CX gates for testing.""" @@ -289,9 +289,13 @@ def count_cx(qc): circuit_in.p(3, 2) circuit_in.cx(1, 2) - pass_ = TemplateOptimization(template_list=[template_czp2()]) + pass_ = TemplateOptimization( + template_list=[template], + user_cost_dict={"cx": 6, "p": 0, "cu": 8}, + ) circuit_out = PassManager(pass_).run(circuit_in) + #import pdb; pdb.set_trace() np.testing.assert_almost_equal(Operator(circuit_out).data[3, 3], np.exp(-4.0j)) np.testing.assert_almost_equal(Operator(circuit_out).data[7, 7], np.exp(-10.0j)) self.assertEqual(count_cx(circuit_out), 0) # Two matches => no CX gates. @@ -309,7 +313,10 @@ def count_cx(qc): circuit_in.p(3, 2) circuit_in.cx(1, 2) - pass_ = TemplateOptimization(template_list=[template_czp2()]) + pass_ = TemplateOptimization( + template_list=[template], + user_cost_dict={"cx": 6, "p": 0, "cu": 8}, + ) circuit_out = PassManager(pass_).run(circuit_in) self.assertEqual(count_cx(circuit_out), 2) # One match => two CX gates. @@ -359,17 +366,14 @@ def test_unbound_parameters_in_rzx_template(self): pass_ = TemplateOptimization(**rzx_templates(["zz2"])) circuit_out = PassManager(pass_).run(circuit_in) - # this is FALSE if template optimization works - circuit_equiv = circuit_in == circuit_out + # these are NOT equal if template optimization works + self.assertNotEqual(circuit_in, circuit_out) - # however this is TRUE if the operators are the same + # however these are equivalent if the operators are the same phi_set = 0.42 - op_equiv = Operator(circuit_in.bind_parameters({phi: phi_set})).equiv( + self.assertTrue(Operator(circuit_in.bind_parameters({phi: phi_set})).equiv( circuit_out.bind_parameters({phi: phi_set}) - ) - - self.assertEqual(op_equiv and not circuit_equiv, True) - + )) if __name__ == "__main__": unittest.main() From f01a671d14ae5c80f46e027809b4b814922783c8 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Thu, 17 Feb 2022 17:20:43 -0500 Subject: [PATCH 67/84] fixed up template matching unit tests to remove calls to UnitaryGate --- .../transpiler/test_template_matching.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index a53a00262838..8910bfbf6e1e 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -16,10 +16,8 @@ import unittest import numpy as np from qiskit import QuantumRegister, QuantumCircuit -from qiskit.circuit import Parameter, Gate -from qiskit.extensions import UnitaryGate +from qiskit.circuit import Parameter from qiskit.quantum_info import Operator -from qiskit.circuit.library import CUGate from qiskit.circuit.library.templates import template_nct_2a_2, template_nct_5a_3 from qiskit.converters.circuit_to_dag import circuit_to_dag from qiskit.converters.circuit_to_dagdependency import circuit_to_dagdependency @@ -230,7 +228,7 @@ def test_parametric_template(self): Check matching where template has parameters. ┌───────────┐ ┌────────┐ q_0: ┤ P(-1.0*β) ├──■────────────■──┤0 ├ - ├───────────┤┌─┴─┐┌──────┐┌─┴─┐│ CZ(β) │ + ├───────────┤┌─┴─┐┌──────┐┌─┴─┐│ CU(2β)│ q_1: ┤ P(-1.0*β) ├┤ X ├┤ P(β) ├┤ X ├┤1 ├ └───────────┘└───┘└──────┘└───┘└────────┘ First test try match on @@ -251,7 +249,7 @@ def test_parametric_template(self): └──────┘ └───┘└──────┘└───┘ """ - #class CZp(Gate): + # class CZp(Gate): # """CZ gates used for the test.""" # # def __init__(self, num_qubits, params): @@ -263,7 +261,6 @@ def test_parametric_template(self): # inverse.name = "icz" # return inverse - beta = Parameter("β") template = QuantumCircuit(2) template.p(-beta, 0) @@ -271,7 +268,7 @@ def test_parametric_template(self): template.cx(0, 1) template.p(beta, 1) template.cx(0, 1) - template.cu(0, 0, 0, beta, 0, 1) + template.cu(0, 2.0 * beta, 0, 0, 0, 1) def count_cx(qc): """Counts the number of CX gates for testing.""" @@ -295,7 +292,7 @@ def count_cx(qc): ) circuit_out = PassManager(pass_).run(circuit_in) - #import pdb; pdb.set_trace() + # import pdb; pdb.set_trace() np.testing.assert_almost_equal(Operator(circuit_out).data[3, 3], np.exp(-4.0j)) np.testing.assert_almost_equal(Operator(circuit_out).data[7, 7], np.exp(-10.0j)) self.assertEqual(count_cx(circuit_out), 0) # Two matches => no CX gates. @@ -371,9 +368,12 @@ def test_unbound_parameters_in_rzx_template(self): # however these are equivalent if the operators are the same phi_set = 0.42 - self.assertTrue(Operator(circuit_in.bind_parameters({phi: phi_set})).equiv( - circuit_out.bind_parameters({phi: phi_set}) - )) + self.assertTrue( + Operator(circuit_in.bind_parameters({phi: phi_set})).equiv( + circuit_out.bind_parameters({phi: phi_set}) + ) + ) + if __name__ == "__main__": unittest.main() From 78a2f47c97a46102db1a78e7c5ae70ab3a57cf8a Mon Sep 17 00:00:00 2001 From: nick bronn Date: Tue, 22 Feb 2022 20:51:57 -0400 Subject: [PATCH 68/84] Update qiskit/dagcircuit/dagcircuit.py Co-authored-by: Jake Lishman --- qiskit/dagcircuit/dagcircuit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 15a86176d5c4..62f603695c39 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -205,7 +205,6 @@ def add_calibration(self, gate, qubits, schedule, params=None): Raises: Exception: if the gate is of type string and params is None. """ - if isinstance(gate, Gate): self._calibrations[gate.name][ (tuple(qubits), tuple(float(p) for p in gate.params)) From 8496a8cccb249a409816d7ae4fd5deb2d2bc7656 Mon Sep 17 00:00:00 2001 From: nick bronn Date: Thu, 24 Feb 2022 10:29:49 -0400 Subject: [PATCH 69/84] Update qiskit/extensions/unitary.py Co-authored-by: Jake Lishman --- qiskit/extensions/unitary.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index b9b0b46ca38a..aa2b84efeb28 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -56,7 +56,6 @@ def __init__(self, data, label=None): # the object to an Operator so that we can extract the underlying # numpy matrix from `Operator.data`. data = data.to_operator().data - # Convert to numpy array in case not already an array data = numpy.array(data, dtype=complex) # Check input is unitary From 82470fd6147d40e9aa552f744f2e3a6a42eefa29 Mon Sep 17 00:00:00 2001 From: nick bronn Date: Thu, 24 Feb 2022 10:30:34 -0400 Subject: [PATCH 70/84] Update qiskit/extensions/unitary.py Co-authored-by: Jake Lishman --- qiskit/extensions/unitary.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index aa2b84efeb28..4eba5ccd8308 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -61,7 +61,6 @@ def __init__(self, data, label=None): # Check input is unitary if not is_unitary_matrix(data): raise ExtensionError("Input matrix is not unitary.") - # Check input is N-qubit matrix input_dim, output_dim = data.shape num_qubits = int(numpy.log2(input_dim)) From 1f17aff258f6fc3ce7e364a313a8485d204f8c12 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Sat, 26 Feb 2022 21:10:10 -0500 Subject: [PATCH 71/84] added template test with two parameters and new logic in the case there are duplicate parameters in the circuit and template --- .../template_substitution.py | 65 ++++++++-- ...emplate-substitution-a1379cdbfcc10b5c.yaml | 2 +- .../transpiler/test_template_matching.py | 115 +++++++++++++++--- 3 files changed, 154 insertions(+), 28 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 3aa96bda2ba7..10004df44219 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -15,8 +15,10 @@ them in circuit and creates a new optimized dag version of the circuit. """ import copy +import random +import string -from qiskit.circuit import ParameterExpression +from qiskit.circuit import Parameter, ParameterExpression from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.dagcircuit.dagdependency import DAGDependency from qiskit.converters.dagdependency_to_dag import dagdependency_to_dag @@ -425,7 +427,6 @@ def run_dag_opt(self): cargs = [] node = group.template_dag_dep.get_node(index) inst = node.op.copy() - dag_dep_opt.add_op_node(inst.inverse(), qargs, cargs) # Add the unmatched gates. @@ -492,13 +493,59 @@ def _attempt_bind(self, template_sublist, circuit_sublist): from sympy.parsing.sympy_parser import parse_expr circuit_params, template_params = [], [] + circ_param_str = "" + sub_params = {} template_dag_dep = copy.deepcopy(self.template_dag_dep) + # add parameters from circuit to circuit_params, as well as a + # string containing parameters to check for duplicates from the template for idx, t_idx in enumerate(template_sublist): qc_idx = circuit_sublist[idx] circuit_params += self.circuit_dag_dep.get_node(qc_idx).op.params - template_params += template_dag_dep.get_node(t_idx).op.params + for param_exp in self.circuit_dag_dep.get_node(qc_idx).op.params: + if isinstance(param_exp, ParameterExpression): + circ_param_str += str(param_exp) + + # add parameters from template to template_params, as well as replace + # node parameters that have duplicate names in the circuit params + # create a dict of sub_params to substitute remaining nodes not in + # the template sublist + for idx, t_idx in enumerate(template_sublist): + node = template_dag_dep.get_node(t_idx) + sub_node_params = [] + for t_param_exp in node.op.params: + if isinstance(t_param_exp, ParameterExpression): + for t_param in t_param_exp.parameters: + if t_param.name in circ_param_str: + new_param_name = "".join( + random.choice(string.ascii_lowercase) for i in range(8) + ) + sub_params[t_param] = Parameter(new_param_name) + t_param_exp = t_param_exp.assign(t_param, sub_params[t_param]) + sub_node_params.append(t_param_exp) + template_params.append(t_param_exp) + else: + sub_node_params.append(t_param_exp) + template_params.append(t_param_exp) + + node.op.params = sub_node_params + + for node in template_dag_dep.get_nodes(): + sub_node_params = [] + for param_exp in node.op.params: + if isinstance(param_exp, ParameterExpression): + for param in param_exp.parameters: + if param in sub_params: + sub_node_params.append( + param_exp.subs(sub_params) + ) # prolly need to specify exact entry + else: + sub_node_params.append(param_exp) + else: + sub_node_params.append(param_exp) + + node.op.params = sub_node_params # Create the fake binding dict and check equations, circ_dict, temp_symbols, sol, fake_bind = [], {}, {}, {}, {} @@ -536,12 +583,14 @@ def _attempt_bind(self, template_sublist, circuit_sublist): for node in template_dag_dep.get_nodes(): bound_params = [] - for param in node.op.params: - if isinstance(param, ParameterExpression): - for key in fake_bind: - bound_params.append(param.assign(key, fake_bind[key])) + for param_exp in node.op.params: + if isinstance(param_exp, ParameterExpression): + for param in param_exp.parameters: + if param in fake_bind: + if fake_bind[param] not in bound_params: + bound_params.append(param_exp.assign(param, fake_bind[param])) else: - bound_params.append(param) + bound_params.append(float(param_exp)) node.op.params = bound_params diff --git a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml index bcc1da8febb0..e552c1f8e344 100644 --- a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml +++ b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml @@ -14,7 +14,7 @@ features: from qiskit.transpiler.passes import TemplateOptimization # New contributions to the template optimization - from qiskit.transpiler.passes.scheduling import RZXCalibrationBuilder, rzx_templates + from qiskit.transpiler.passes.calibration import RZXCalibrationBuilder, rzx_templates from qiskit.test.mock import FakeCasablanca backend = FakeCasablanca() diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index 8910bfbf6e1e..7ea0f468bdcb 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -21,6 +21,7 @@ from qiskit.circuit.library.templates import template_nct_2a_2, template_nct_5a_3 from qiskit.converters.circuit_to_dag import circuit_to_dag from qiskit.converters.circuit_to_dagdependency import circuit_to_dagdependency +from qiskit.qasm import pi from qiskit.transpiler import PassManager from qiskit.transpiler.passes import TemplateOptimization from qiskit.transpiler.passes.calibration.rzx_templates import rzx_templates @@ -249,18 +250,6 @@ def test_parametric_template(self): └──────┘ └───┘└──────┘└───┘ """ - # class CZp(Gate): - # """CZ gates used for the test.""" - # - # def __init__(self, num_qubits, params): - # super().__init__("cz", num_qubits, params) - # - # def inverse(self): - # #inverse = UnitaryGate(np.diag([1.0, 1.0, 1.0, np.exp(-2.0j * self.params[0])])) - # inverse = CUGate(0, 0, 0, -2.0j * self.params[0]) - # inverse.name = "icz" - # return inverse - beta = Parameter("β") template = QuantumCircuit(2) template.p(-beta, 0) @@ -292,7 +281,6 @@ def count_cx(qc): ) circuit_out = PassManager(pass_).run(circuit_in) - # import pdb; pdb.set_trace() np.testing.assert_almost_equal(Operator(circuit_out).data[3, 3], np.exp(-4.0j)) np.testing.assert_almost_equal(Operator(circuit_out).data[7, 7], np.exp(-10.0j)) self.assertEqual(count_cx(circuit_out), 0) # Two matches => no CX gates. @@ -351,13 +339,15 @@ def test_optimizer_does_not_replace_unbound_partial_match(self): def test_unbound_parameters_in_rzx_template(self): """ Test that rzx template ('zz2') functions correctly for a simple - circuit with an unbound ParameterExpression. + circuit with an unbound ParameterExpression. This uses the same + Parameter (theta) as the template, so this also checks that template + substitution handle this correctly. """ - phi = Parameter("$\\phi$") + theta = Parameter("ϴ") circuit_in = QuantumCircuit(2) circuit_in.cx(0, 1) - circuit_in.p(2 * phi, 1) + circuit_in.p(2 * theta, 1) circuit_in.cx(0, 1) pass_ = TemplateOptimization(**rzx_templates(["zz2"])) @@ -367,10 +357,97 @@ def test_unbound_parameters_in_rzx_template(self): self.assertNotEqual(circuit_in, circuit_out) # however these are equivalent if the operators are the same - phi_set = 0.42 + theta_set = 0.42 + self.assertTrue( + Operator(circuit_in.bind_parameters({theta: theta_set})).equiv( + circuit_out.bind_parameters({theta: theta_set}) + ) + ) + + def test_two_parameter_template(self): + """ + Test a two-Parameter template based on rzx_templates(["zz3"]), + + ┌───┐┌───────┐┌───┐┌────────────┐» + q_0: ──■─────────────■──┤ X ├┤ Rz(φ) ├┤ X ├┤ Rz(-1.0*φ) ├» + ┌─┴─┐┌───────┐┌─┴─┐└─┬─┘└───────┘└─┬─┘└────────────┘» + q_1: ┤ X ├┤ Rz(θ) ├┤ X ├──■─────────────■────────────────» + └───┘└───────┘└───┘ + « ┌─────────┐┌─────────┐┌─────────┐┌───────────┐┌──────────────┐» + «q_0: ┤ Rz(π/2) ├┤ Rx(π/2) ├┤ Rz(π/2) ├┤ Rx(1.0*φ) ├┤1 ├» + « └─────────┘└─────────┘└─────────┘└───────────┘│ Rzx(-1.0*φ) │» + «q_1: ──────────────────────────────────────────────┤0 ├» + « └──────────────┘» + « ┌─────────┐ ┌─────────┐┌─────────┐ » + «q_0: ─┤ Rz(π/2) ├──┤ Rx(π/2) ├┤ Rz(π/2) ├────────────────────────» + « ┌┴─────────┴─┐├─────────┤├─────────┤┌─────────┐┌───────────┐» + «q_1: ┤ Rz(-1.0*θ) ├┤ Rz(π/2) ├┤ Rx(π/2) ├┤ Rz(π/2) ├┤ Rx(1.0*θ) ├» + « └────────────┘└─────────┘└─────────┘└─────────┘└───────────┘» + « ┌──────────────┐ + «q_0: ┤0 ├───────────────────────────────── + « │ Rzx(-1.0*θ) │┌─────────┐┌─────────┐┌─────────┐ + «q_1: ┤1 ├┤ Rz(π/2) ├┤ Rx(π/2) ├┤ Rz(π/2) ├ + « └──────────────┘└─────────┘└─────────┘└─────────┘ + + correctly template matches into a unique circuit, but that it is + equivalent to the input circuit when the Parameters are bound to floats + and checked with Operator equivalence. + """ + theta = Parameter("θ") + phi = Parameter("φ") + + template = QuantumCircuit(2) + template.cx(0, 1) + template.rz(theta, 1) + template.cx(0, 1) + template.cx(1, 0) + template.rz(phi, 0) + template.cx(1, 0) + template.rz(-phi, 0) + template.rz(pi / 2, 0) + template.rx(pi / 2, 0) + template.rz(pi / 2, 0) + template.rx(phi, 0) + template.rzx(-phi, 1, 0) + template.rz(pi / 2, 0) + template.rz(-theta, 1) + template.rx(pi / 2, 0) + template.rz(pi / 2, 1) + template.rz(pi / 2, 0) + template.rx(pi / 2, 1) + template.rz(pi / 2, 1) + template.rx(theta, 1) + template.rzx(-theta, 0, 1) + template.rz(pi / 2, 1) + template.rx(pi / 2, 1) + template.rz(pi / 2, 1) + + alpha = Parameter("$\\alpha$") + beta = Parameter("$\\beta$") + + circuit_in = QuantumCircuit(2) + circuit_in.cx(0, 1) + circuit_in.rz(2 * alpha, 1) + circuit_in.cx(0, 1) + circuit_in.cx(1, 0) + circuit_in.rz(3 * beta, 0) + circuit_in.cx(1, 0) + + pass_ = TemplateOptimization( + [template], + user_cost_dict={"cx": 6, "rz": 0, "rx": 1, "rzx": 0}, + ) + circuit_out = PassManager(pass_).run(circuit_in) + + # these are NOT equal if template optimization works + self.assertNotEqual(circuit_in, circuit_out) + + # however these are equivalent if the operators are the same + alpha_set = 0.39 + beta_set = 0.42 self.assertTrue( - Operator(circuit_in.bind_parameters({phi: phi_set})).equiv( - circuit_out.bind_parameters({phi: phi_set}) + Operator(circuit_in.bind_parameters({alpha: alpha_set, beta: beta_set})).equiv( + circuit_out.bind_parameters({alpha: alpha_set, beta: beta_set}) ) ) From fb8ca5c4b73d6845ede3496c85d7355bd6adb575 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Tue, 1 Mar 2022 12:55:43 -0500 Subject: [PATCH 72/84] added two-parameter unit test and a check for overlapping parameters between circuits and templates --- .../probability_distributions/__init__.py | 11 ---------- qiskit/circuit/parameterexpression.py | 21 ++++++------------- .../template_substitution.py | 17 +++++++++++---- .../transpiler/test_template_matching.py | 15 +++++++++---- 4 files changed, 30 insertions(+), 34 deletions(-) delete mode 100644 qiskit/circuit/library/probability_distributions/__init__.py diff --git a/qiskit/circuit/library/probability_distributions/__init__.py b/qiskit/circuit/library/probability_distributions/__init__.py deleted file mode 100644 index 9a29547928ea..000000000000 --- a/qiskit/circuit/library/probability_distributions/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 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. diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 4fef9c89cca0..cd9ee5a9738e 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -19,16 +19,12 @@ import numpy -from qiskit.circuit.exceptions import CircuitError, ParameterTypeError +from qiskit.circuit.exceptions import CircuitError from qiskit.utils import optionals as _optionals -try: +if _optionals.HAS_SYMENGINE: import symengine - _optionals.HAS_SYMENGINE = True -except ImportError: - _optionals.HAS_SYMENGINE = False - # This type is redefined at the bottom to insert the full reference to "ParameterExpression", so it # can safely be used by runtime type-checkers like Sphinx. Mypy does not need this because it # handles the references by static analysis. @@ -436,7 +432,7 @@ def __complex__(self): return complex(self._symbol_expr) # TypeError is for sympy, RuntimeError for symengine except (TypeError, RuntimeError) as exc: - raise ParameterTypeError( + raise TypeError( "ParameterExpression with unbound parameters ({}) " "cannot be cast to a complex.".format(self.parameters) ) from exc @@ -446,7 +442,7 @@ def __float__(self): return float(self._symbol_expr) # TypeError is for sympy, RuntimeError for symengine except (TypeError, RuntimeError) as exc: - raise ParameterTypeError( + raise TypeError( "ParameterExpression with unbound parameters ({}) " "cannot be cast to a float.".format(self.parameters) ) from exc @@ -535,14 +531,9 @@ def is_real(self): return False return True - def to_simplify_expression(self): + def to_sympify_expression(self): """Return symbolic expression from sympy/symengine""" - if _optionals.HAS_SYMENGINE: - return symengine.sympify(self._symbol_expr) - else: - from sympy import sympify - - return sympify(self._symbol_expr) + return self._symbol_expr # Redefine the type so external imports get an evaluated reference; Sphinx needs this to understand diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 10004df44219..dcabc36dce2c 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -480,6 +480,15 @@ def _attempt_bind(self, template_sublist, circuit_sublist): solution is found then the match is valid and the parameters are assigned. If not, None is returned. + In order to resolve the conflict of the same parameter names in the + circuit and template, each variable in the template sublist is + checked against circ_param_str (the string of all combined + parameter expressions in the circuit sublist). If a Parameter from + the template sublist has the same name, a new Parameter with a + random name is created and assigned to the template sublist and added + to a dictionary of substituted params (sub_params), so that the + substituted Parameter can be replaced in every tempalte node. + Args: template_sublist (list): part of the matched template. circuit_sublist (list): part of the matched circuit. @@ -552,17 +561,17 @@ def _attempt_bind(self, template_sublist, circuit_sublist): for circuit_param, template_param in zip(circuit_params, template_params): if isinstance(template_param, ParameterExpression): if isinstance(circuit_param, ParameterExpression): - circ_param_sym = circuit_param.to_simplify_expression() + circ_param_sym = circuit_param.to_sympify_expression() else: circ_param_sym = parse_expr(str(circuit_param)) - equations.append(sym.Eq(template_param.to_simplify_expression(), circ_param_sym)) + equations.append(sym.Eq(template_param.to_sympify_expression(), circ_param_sym)) for param in template_param.parameters: - temp_symbols[param] = param.to_simplify_expression() + temp_symbols[param] = param.to_sympify_expression() if isinstance(circuit_param, ParameterExpression): for param in circuit_param.parameters: - circ_dict[param] = param.to_simplify_expression() + circ_dict[param] = param.to_sympify_expression() if not temp_symbols: return template_dag_dep diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index 7ea0f468bdcb..0d4e208add14 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -222,7 +222,11 @@ def test_accept_dagdependency(self): pass_ = TemplateOptimization(template_list=templates) circuit_out = PassManager(pass_).run(circuit_in) - self.assertEqual(circuit_out.count_ops().get("cx", 0), 0) + # these are NOT equal if template optimization works + self.assertNotEqual(circuit_in, circuit_out) + + # however these are equivalent if the operators are the same + self.assertTrue(Operator(circuit_in).equiv(circuit_out)) def test_parametric_template(self): """ @@ -304,8 +308,11 @@ def count_cx(qc): ) circuit_out = PassManager(pass_).run(circuit_in) - self.assertEqual(count_cx(circuit_out), 2) # One match => two CX gates. - np.testing.assert_almost_equal(Operator(circuit_in).data, Operator(circuit_out).data) + # these are NOT equal if template optimization works + self.assertNotEqual(circuit_in, circuit_out) + + # however these are equivalent if the operators are the same + self.assertTrue(Operator(circuit_in).equiv(circuit_out)) def test_optimizer_does_not_replace_unbound_partial_match(self): """ @@ -443,7 +450,7 @@ def test_two_parameter_template(self): self.assertNotEqual(circuit_in, circuit_out) # however these are equivalent if the operators are the same - alpha_set = 0.39 + alpha_set = 0.37 beta_set = 0.42 self.assertTrue( Operator(circuit_in.bind_parameters({alpha: alpha_set, beta: beta_set})).equiv( From 5de03842c16f689c89efc6fdfbacbd0878423258 Mon Sep 17 00:00:00 2001 From: Nick Bronn Date: Thu, 3 Mar 2022 11:53:38 -0500 Subject: [PATCH 73/84] remove ParameterTypeeException from exceptions.py --- qiskit/circuit/exceptions.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/qiskit/circuit/exceptions.py b/qiskit/circuit/exceptions.py index ab92a65cfe0c..b3a06ede2380 100644 --- a/qiskit/circuit/exceptions.py +++ b/qiskit/circuit/exceptions.py @@ -19,9 +19,3 @@ class CircuitError(QiskitError): """Base class for errors raised while processing a circuit.""" pass - - -class ParameterTypeError(TypeError): - """The type error throw by parameter if we try to cast to number unbind parameter.""" - - pass From 79060364c1442f564c87a7b31fd47afc211e82f8 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 24 Mar 2022 12:13:14 +0000 Subject: [PATCH 74/84] Restore lazy symengine imports Use of `_optionals.HAS_SYMENGINE` is intended to be within run-time locations; putting it at the top level of a file causes this fairly heavy library to be imported at runtime, slowing down `import qiskit` for those who won't use that functionality. --- qiskit/circuit/parameterexpression.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 48627c48bdec..d01062336513 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -22,9 +22,6 @@ from qiskit.circuit.exceptions import CircuitError from qiskit.utils import optionals as _optionals -if _optionals.HAS_SYMENGINE: - import symengine - # This type is redefined at the bottom to insert the full reference to "ParameterExpression", so it # can safely be used by runtime type-checkers like Sphinx. Mypy does not need this because it # handles the references by static analysis. @@ -68,6 +65,8 @@ def _names(self) -> Dict: def conjugate(self) -> "ParameterExpression": """Return the conjugate.""" if _optionals.HAS_SYMENGINE: + import symengine + conjugated = ParameterExpression( self._parameter_symbols, symengine.conjugate(self._symbol_expr) ) @@ -167,6 +166,8 @@ def subs(self, parameter_map: Dict) -> "ParameterExpression": self._raise_if_passed_unknown_parameters(parameter_map.keys()) self._raise_if_parameter_names_conflict(inbound_names, parameter_map.keys()) if _optionals.HAS_SYMENGINE: + import symengine + new_parameter_symbols = {p: symengine.Symbol(p.name) for p in inbound_parameters} else: from sympy import Symbol @@ -289,6 +290,8 @@ def gradient(self, param) -> Union["ParameterExpression", complex]: # Compute the gradient of the parameter expression w.r.t. param key = self._parameter_symbols[param] if _optionals.HAS_SYMENGINE: + import symengine + expr_grad = symengine.Derivative(self._symbol_expr, key) else: # TODO enable nth derivative @@ -348,6 +351,8 @@ def _call(self, ufunc): def sin(self): """Sine of a ParameterExpression""" if _optionals.HAS_SYMENGINE: + import symengine + return self._call(symengine.sin) else: from sympy import sin as _sin @@ -357,6 +362,8 @@ def sin(self): def cos(self): """Cosine of a ParameterExpression""" if _optionals.HAS_SYMENGINE: + import symengine + return self._call(symengine.cos) else: from sympy import cos as _cos @@ -366,6 +373,8 @@ def cos(self): def tan(self): """Tangent of a ParameterExpression""" if _optionals.HAS_SYMENGINE: + import symengine + return self._call(symengine.tan) else: from sympy import tan as _tan @@ -375,6 +384,8 @@ def tan(self): def arcsin(self): """Arcsin of a ParameterExpression""" if _optionals.HAS_SYMENGINE: + import symengine + return self._call(symengine.asin) else: from sympy import asin as _asin @@ -384,6 +395,8 @@ def arcsin(self): def arccos(self): """Arccos of a ParameterExpression""" if _optionals.HAS_SYMENGINE: + import symengine + return self._call(symengine.acos) else: from sympy import acos as _acos @@ -393,6 +406,8 @@ def arccos(self): def arctan(self): """Arctan of a ParameterExpression""" if _optionals.HAS_SYMENGINE: + import symengine + return self._call(symengine.atan) else: from sympy import atan as _atan @@ -402,6 +417,7 @@ def arctan(self): def exp(self): """Exponential of a ParameterExpression""" if _optionals.HAS_SYMENGINE: + import symengine return self._call(symengine.exp) else: @@ -412,6 +428,7 @@ def exp(self): def log(self): """Logarithm of a ParameterExpression""" if _optionals.HAS_SYMENGINE: + import symengine return self._call(symengine.log) else: From 092f92bc03edda18f70520e373520d2dab345ef1 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 24 Mar 2022 12:25:00 +0000 Subject: [PATCH 75/84] Rename to_sympify_expression to sympify --- qiskit/circuit/parameterexpression.py | 14 ++++++++++++-- .../template_matching/template_substitution.py | 8 ++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index d01062336513..641a7404c553 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -521,8 +521,18 @@ def is_real(self): return False return True - def to_sympify_expression(self): - """Return symbolic expression from sympy/symengine""" + def sympify(self): + """Return symbolic expression as a raw Sympy or Symengine object. + + Symengine is used preferentially; if both are available, the result will always be a + ``symengine`` object. Symengine is a separate library but has integration with Sympy. + + .. note:: + + This is for interoperability only. Qiskit will not accept or work with raw Sympy or + Symegine expressions in its parameters, because they do not contain the tracking + information used in circuit-parameter binding and assignment. + """ return self._symbol_expr diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index dcabc36dce2c..1e47acec8b5d 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -561,17 +561,17 @@ def _attempt_bind(self, template_sublist, circuit_sublist): for circuit_param, template_param in zip(circuit_params, template_params): if isinstance(template_param, ParameterExpression): if isinstance(circuit_param, ParameterExpression): - circ_param_sym = circuit_param.to_sympify_expression() + circ_param_sym = circuit_param.sympify() else: circ_param_sym = parse_expr(str(circuit_param)) - equations.append(sym.Eq(template_param.to_sympify_expression(), circ_param_sym)) + equations.append(sym.Eq(template_param.sympify(), circ_param_sym)) for param in template_param.parameters: - temp_symbols[param] = param.to_sympify_expression() + temp_symbols[param] = param.sympify() if isinstance(circuit_param, ParameterExpression): for param in circuit_param.parameters: - circ_dict[param] = param.to_sympify_expression() + circ_dict[param] = param.sympify() if not temp_symbols: return template_dag_dep From 95dbcfb435587e71bd0949ff159cdea91d59db93 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 24 Mar 2022 12:38:32 +0000 Subject: [PATCH 76/84] Revert now-unnecessary changes to calibration builder --- qiskit/transpiler/passes/calibration/builders.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/qiskit/transpiler/passes/calibration/builders.py b/qiskit/transpiler/passes/calibration/builders.py index e5202f53cd18..cdb8be5e1bbe 100644 --- a/qiskit/transpiler/passes/calibration/builders.py +++ b/qiskit/transpiler/passes/calibration/builders.py @@ -25,7 +25,6 @@ from qiskit.circuit.library.standard_gates import RZXGate from qiskit.dagcircuit import DAGCircuit from qiskit.exceptions import QiskitError -from qiskit.transpiler.exceptions import TranspilerError from qiskit.pulse import ( Play, Delay, @@ -225,18 +224,10 @@ def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, schedule: The calibration schedule for the RZXGate(theta). Raises: - QiskitError: If the control and target - qubits cannot be identified, or the backend does not support cx between - the qubits. - TranspilerError: If all Parameters are not bound. + QiskitError: if the control and target qubits cannot be identified or the backend + does not support cx between the qubits. """ - try: - theta = float(node_op.params[0]) - except TypeError as ex: - raise TranspilerError( - "This transpilation pass requires all Parameters to be bound and real." - ) from ex - + theta = node_op.params[0] q1, q2 = qubits[0], qubits[1] if not self._inst_map.has("cx", qubits): From 908f87d38abc531d962a04c01179f3daef36bdb6 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 24 Mar 2022 12:42:50 +0000 Subject: [PATCH 77/84] Fixup release note --- ...-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml index e552c1f8e344..c93051834649 100644 --- a/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml +++ b/releasenotes/notes/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml @@ -72,7 +72,3 @@ features: 1600 Duration of standard with two CNOT gates: 6848 - - Note that the :class:`.Parameter`\s/:class:`.ParameterExpression`\s - must be bound before the :class:`.RZXCalibrationBuilder` can define - calibrations for the :class:`.RZXGate`\ s. From fc8315688ffb5ebca7e56c9b6f72887e90879dea Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 24 Mar 2022 18:18:49 +0000 Subject: [PATCH 78/84] Add explicit tests of template matching pass This adds several tests of the exact form produced by running the template-matching transpiler pass, including those with purely numeric circuits and those with symbolic parameters. --- .../transpiler/test_template_matching.py | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index 0d4e208add14..53e5e465b392 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -29,6 +29,25 @@ from qiskit.transpiler.exceptions import TranspilerError +def _ry_to_rz_template_pass(parameter=None, extra_costs=None): + """Create a simple pass manager that runs a template optimisation with a single transformation. + It turns ``RX(pi/2).RY(parameter).RX(-pi/2)`` into the equivalent virtual ``RZ`` rotation, where + if ``parameter`` is given, it will be the instance used in the template.""" + if parameter is None: + parameter = Parameter("_ry_rz_template_inner") + template = QuantumCircuit(1) + template.rx(-np.pi / 2, 0) + template.ry(parameter, 0) + template.rx(np.pi / 2, 0) + template.rz(-parameter, 0) + + costs = {"rx": 16, "ry": 16, "rz": 0} + if extra_costs is not None: + costs.update(extra_costs) + + return PassManager(TemplateOptimization([template], user_cost_dict=costs)) + + class TestTemplateMatching(QiskitTestCase): """Test the TemplateOptimization pass.""" @@ -458,6 +477,135 @@ def test_two_parameter_template(self): ) ) + def test_exact_substitution_numeric_parameter(self): + """Test that a template match produces the expected value for numeric parameters.""" + circuit_in = QuantumCircuit(1) + circuit_in.rx(-np.pi / 2, 0) + circuit_in.ry(1.45, 0) + circuit_in.rx(np.pi / 2, 0) + circuit_out = _ry_to_rz_template_pass().run(circuit_in) + + expected = QuantumCircuit(1) + expected.rz(1.45, 0) + self.assertEqual(circuit_out, expected) + + def test_exact_substitution_symbolic_parameter(self): + """Test that a template match produces the expected value for numeric parameters.""" + a_circuit = Parameter("a") + circuit_in = QuantumCircuit(1) + circuit_in.h(0) + circuit_in.rx(-np.pi / 2, 0) + circuit_in.ry(a_circuit, 0) + circuit_in.rx(np.pi / 2, 0) + circuit_out = _ry_to_rz_template_pass(extra_costs={"h": 1}).run(circuit_in) + + expected = QuantumCircuit(1) + expected.h(0) + expected.rz(a_circuit, 0) + self.assertEqual(circuit_out, expected) + + def test_naming_clash(self): + """Test that the template matching works and correctly replaces a template if there is a + naming clash between it and the circuit. This should include binding a partial match with a + parameter.""" + # Two instances of parameters with the same name---this is how naming clashes might occur. + a_template = Parameter("a") + a_circuit = Parameter("a") + circuit_in = QuantumCircuit(1) + circuit_in.h(0) + circuit_in.rx(-np.pi / 2, 0) + circuit_in.ry(a_circuit, 0) + circuit_in.rx(np.pi / 2, 0) + circuit_out = _ry_to_rz_template_pass(a_template, extra_costs={"h": 1}).run(circuit_in) + + expected = QuantumCircuit(1) + expected.h(0) + expected.rz(a_circuit, 0) + self.assertEqual(circuit_out, expected) + # Ensure that the bound parameter in the output is referentially the same as the one we put + # in the input circuit.. + self.assertEqual(len(circuit_out.parameters), 1) + self.assertIs(circuit_in.parameters[0], a_circuit) + self.assertIs(circuit_out.parameters[0], a_circuit) + + def test_naming_clash_in_expression(self): + """Test that the template matching works and correctly replaces a template if there is a + naming clash between it and the circuit. This should include binding a partial match with a + parameter.""" + a_template = Parameter("a") + a_circuit = Parameter("a") + circuit_in = QuantumCircuit(1) + circuit_in.h(0) + circuit_in.rx(-np.pi / 2, 0) + circuit_in.ry(2 * a_circuit, 0) + circuit_in.rx(np.pi / 2, 0) + circuit_out = _ry_to_rz_template_pass(a_template, extra_costs={"h": 1}).run(circuit_in) + + expected = QuantumCircuit(1) + expected.h(0) + expected.rz(2 * a_circuit, 0) + self.assertEqual(circuit_out, expected) + # Ensure that the bound parameter in the output is referentially the same as the one we put + # in the input circuit.. + self.assertEqual(len(circuit_out.parameters), 1) + self.assertIs(circuit_in.parameters[0], a_circuit) + self.assertIs(circuit_out.parameters[0], a_circuit) + + def test_template_match_with_uninvolved_parameter(self): + """Test that the template matching algorithm succeeds at matching a circuit that contains an + unbound parameter that is not involved in the subcircuit that matches.""" + b_circuit = Parameter("b") + circuit_in = QuantumCircuit(2) + circuit_in.rz(b_circuit, 0) + circuit_in.rx(-np.pi / 2, 1) + circuit_in.ry(1.45, 1) + circuit_in.rx(np.pi / 2, 1) + circuit_out = _ry_to_rz_template_pass().run(circuit_in) + + expected = QuantumCircuit(2) + expected.rz(b_circuit, 0) + expected.rz(1.45, 1) + self.assertEqual(circuit_out, expected) + + def test_multiple_numeric_matches_same_template(self): + """Test that the template matching will change both instances of a partial match within a + longer circuit.""" + circuit_in = QuantumCircuit(2) + # Qubit 0 + circuit_in.rx(-np.pi / 2, 0) + circuit_in.ry(1.32, 0) + circuit_in.rx(np.pi / 2, 0) + # Qubit 1 + circuit_in.rx(-np.pi / 2, 1) + circuit_in.ry(2.54, 1) + circuit_in.rx(np.pi / 2, 1) + circuit_out = _ry_to_rz_template_pass().run(circuit_in) + + expected = QuantumCircuit(2) + expected.rz(1.32, 0) + expected.rz(2.54, 1) + self.assertEqual(circuit_out, expected) + + def test_multiple_symbolic_matches_same_template(self): + """Test that the template matching will change both instances of a partial match within a + longer circuit.""" + a, b = Parameter("a"), Parameter("b") + circuit_in = QuantumCircuit(2) + # Qubit 0 + circuit_in.rx(-np.pi / 2, 0) + circuit_in.ry(a, 0) + circuit_in.rx(np.pi / 2, 0) + # Qubit 1 + circuit_in.rx(-np.pi / 2, 1) + circuit_in.ry(b, 1) + circuit_in.rx(np.pi / 2, 1) + circuit_out = _ry_to_rz_template_pass().run(circuit_in) + + expected = QuantumCircuit(2) + expected.rz(a, 0) + expected.rz(b, 1) + self.assertEqual(circuit_out, expected) + if __name__ == "__main__": unittest.main() From a14dc37441f73e8c9630d43b03ba8a1c9de5c757 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 24 Mar 2022 14:58:04 +0000 Subject: [PATCH 79/84] Fix template parameter substitution This fixes issues introduced recently in the PR that caused parameters to be incorrectly bound in the result. This meant that the actual numbers in the produced circuits were incorrect. This happened mostly by tracking data structures being updated at the wrong levels within loops. In addition, this commit also updates some data structures to more robust and efficient versions: - Testing whether a parameter has a clash is best done by constructing a set of parameters used in the input circuits, then testing directly on this, rather than stringifying expressions and using subsearch matches; this avoids problems if two parameters have contained names, or if more than one match is catenated into a single string. - Using a dictionary with a missing-element constructor to build the replacement parameters allows the looping logic to be simpler; the "build missing element" logic can be separated out to happen automatically. --- .../template_substitution.py | 67 +++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 1e47acec8b5d..9eab25363f65 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -14,9 +14,9 @@ Template matching substitution, given a list of maximal matches it substitutes them in circuit and creates a new optimized dag version of the circuit. """ +import collections import copy -import random -import string +import itertools from qiskit.circuit import Parameter, ParameterExpression from qiskit.dagcircuit.dagcircuit import DAGCircuit @@ -502,42 +502,44 @@ def _attempt_bind(self, template_sublist, circuit_sublist): from sympy.parsing.sympy_parser import parse_expr circuit_params, template_params = [], [] - circ_param_str = "" - sub_params = {} + # Set of all parameter names that are present in the circuits to be optimised. + circuit_params_set = set() template_dag_dep = copy.deepcopy(self.template_dag_dep) - # add parameters from circuit to circuit_params, as well as a - # string containing parameters to check for duplicates from the template + # add parameters from circuit to circuit_params for idx, t_idx in enumerate(template_sublist): qc_idx = circuit_sublist[idx] - circuit_params += self.circuit_dag_dep.get_node(qc_idx).op.params - for param_exp in self.circuit_dag_dep.get_node(qc_idx).op.params: - if isinstance(param_exp, ParameterExpression): - circ_param_str += str(param_exp) + parameters = self.circuit_dag_dep.get_node(qc_idx).op.params + circuit_params += parameters + for parameter in parameters: + if isinstance(parameter, ParameterExpression): + circuit_params_set.update(x.name for x in parameter.parameters) + + _dummy_counter = itertools.count() + + def dummy_parameter(): + # Strictly not _guaranteed_ to avoid naming clashes, but if someone's calling their + # parameters this then that's their own fault. + return Parameter(f"_qiskit_template_dummy_{next(_dummy_counter)}") + + # Substitutions for parameters that have clashing names between the input circuits and the + # defined templates. + template_clash_substitutions = collections.defaultdict(dummy_parameter) - # add parameters from template to template_params, as well as replace - # node parameters that have duplicate names in the circuit params - # create a dict of sub_params to substitute remaining nodes not in - # the template sublist + # add parameters from template to template_params, replacing parameters with names that + # clash with those in the circuit. for idx, t_idx in enumerate(template_sublist): node = template_dag_dep.get_node(t_idx) sub_node_params = [] for t_param_exp in node.op.params: if isinstance(t_param_exp, ParameterExpression): for t_param in t_param_exp.parameters: - if t_param.name in circ_param_str: - new_param_name = "".join( - random.choice(string.ascii_lowercase) for i in range(8) - ) - sub_params[t_param] = Parameter(new_param_name) - t_param_exp = t_param_exp.assign(t_param, sub_params[t_param]) - sub_node_params.append(t_param_exp) - template_params.append(t_param_exp) - else: - sub_node_params.append(t_param_exp) - template_params.append(t_param_exp) - + if t_param.name in circuit_params_set: + new_param = template_clash_substitutions[t_param.name] + t_param_exp = t_param_exp.assign(t_param, new_param) + sub_node_params.append(t_param_exp) + template_params.append(t_param_exp) node.op.params = sub_node_params for node in template_dag_dep.get_nodes(): @@ -545,14 +547,11 @@ def _attempt_bind(self, template_sublist, circuit_sublist): for param_exp in node.op.params: if isinstance(param_exp, ParameterExpression): for param in param_exp.parameters: - if param in sub_params: - sub_node_params.append( - param_exp.subs(sub_params) - ) # prolly need to specify exact entry - else: - sub_node_params.append(param_exp) - else: - sub_node_params.append(param_exp) + if param.name in template_clash_substitutions: + param_exp = param_exp.assign( + param, template_clash_substitutions[param.name] + ) + sub_node_params.append(param_exp) node.op.params = sub_node_params From 7034789aec08c1d906da6bfe3f29e77a38d06d94 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 24 Mar 2022 18:54:16 +0000 Subject: [PATCH 80/84] Fix overlooked documentation comment --- .../template_matching/template_substitution.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 9eab25363f65..c1c7dbc93d8f 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -482,12 +482,8 @@ def _attempt_bind(self, template_sublist, circuit_sublist): In order to resolve the conflict of the same parameter names in the circuit and template, each variable in the template sublist is - checked against circ_param_str (the string of all combined - parameter expressions in the circuit sublist). If a Parameter from - the template sublist has the same name, a new Parameter with a - random name is created and assigned to the template sublist and added - to a dictionary of substituted params (sub_params), so that the - substituted Parameter can be replaced in every tempalte node. + re-assigned to a new dummy parameter with a completely separate name + if it clashes with one that exists in an input circuit. Args: template_sublist (list): part of the matched template. From 16bbffb5bb4831776528b75cd8dced1978a9a512 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 24 Mar 2022 19:05:07 +0000 Subject: [PATCH 81/84] Remove qasm.pi import in favour of numpy --- .../transpiler/test_template_matching.py | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index 53e5e465b392..f471911570af 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -21,7 +21,6 @@ from qiskit.circuit.library.templates import template_nct_2a_2, template_nct_5a_3 from qiskit.converters.circuit_to_dag import circuit_to_dag from qiskit.converters.circuit_to_dagdependency import circuit_to_dagdependency -from qiskit.qasm import pi from qiskit.transpiler import PassManager from qiskit.transpiler.passes import TemplateOptimization from qiskit.transpiler.passes.calibration.rzx_templates import rzx_templates @@ -430,23 +429,23 @@ def test_two_parameter_template(self): template.rz(phi, 0) template.cx(1, 0) template.rz(-phi, 0) - template.rz(pi / 2, 0) - template.rx(pi / 2, 0) - template.rz(pi / 2, 0) + template.rz(np.pi / 2, 0) + template.rx(np.pi / 2, 0) + template.rz(np.pi / 2, 0) template.rx(phi, 0) template.rzx(-phi, 1, 0) - template.rz(pi / 2, 0) + template.rz(np.pi / 2, 0) template.rz(-theta, 1) - template.rx(pi / 2, 0) - template.rz(pi / 2, 1) - template.rz(pi / 2, 0) - template.rx(pi / 2, 1) - template.rz(pi / 2, 1) + template.rx(np.pi / 2, 0) + template.rz(np.pi / 2, 1) + template.rz(np.pi / 2, 0) + template.rx(np.pi / 2, 1) + template.rz(np.pi / 2, 1) template.rx(theta, 1) template.rzx(-theta, 0, 1) - template.rz(pi / 2, 1) - template.rx(pi / 2, 1) - template.rz(pi / 2, 1) + template.rz(np.pi / 2, 1) + template.rx(np.pi / 2, 1) + template.rz(np.pi / 2, 1) alpha = Parameter("$\\alpha$") beta = Parameter("$\\beta$") From 317e2a61970d2fdaaffc8933b5eb244005a3d950 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 24 Mar 2022 19:29:03 +0000 Subject: [PATCH 82/84] Add tests of multi-parameter instructions --- .../transpiler/test_template_matching.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index f471911570af..81914912583a 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -605,6 +605,46 @@ def test_multiple_symbolic_matches_same_template(self): expected.rz(b, 1) self.assertEqual(circuit_out, expected) + def test_template_match_multiparameter(self): + """Test that the template matching works on instructions that take more than one + parameter.""" + a = Parameter("a") + b = Parameter("b") + template = QuantumCircuit(1) + template.u(0, a, b, 0) + template.rz(-a - b, 0) + + circuit_in = QuantumCircuit(1) + circuit_in.u(0, 1.23, 2.45, 0) + pm = PassManager(TemplateOptimization([template], user_cost_dict={"u": 16, "rz": 0})) + circuit_out = pm.run(circuit_in) + + expected = QuantumCircuit(1) + expected.rz(1.23 + 2.45, 0) + + self.assertEqual(circuit_out, expected) + + def test_naming_clash_multiparameter(self): + """Test that the naming clash prevention mechanism works with instructions that take + multiple parameters.""" + a_template = Parameter("a") + b_template = Parameter("b") + template = QuantumCircuit(1) + template.u(0, a_template, b_template, 0) + template.rz(-a_template - b_template, 0) + + a_circuit = Parameter("a") + b_circuit = Parameter("b") + circuit_in = QuantumCircuit(1) + circuit_in.u(0, a_circuit, b_circuit, 0) + pm = PassManager(TemplateOptimization([template], user_cost_dict={"u": 16, "rz": 0})) + circuit_out = pm.run(circuit_in) + + expected = QuantumCircuit(1) + expected.rz(a_circuit + b_circuit, 0) + + self.assertEqual(circuit_out, expected) + if __name__ == "__main__": unittest.main() From 6ccc663ded298a06f53227748ba10c0837c3ef02 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 24 Mar 2022 19:29:24 +0000 Subject: [PATCH 83/84] Fix template matching with multiparameter expressions --- .../template_matching/template_substitution.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index c1c7dbc93d8f..2b8cee9dff7a 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -504,7 +504,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): template_dag_dep = copy.deepcopy(self.template_dag_dep) # add parameters from circuit to circuit_params - for idx, t_idx in enumerate(template_sublist): + for idx, _ in enumerate(template_sublist): qc_idx = circuit_sublist[idx] parameters = self.circuit_dag_dep.get_node(qc_idx).op.params circuit_params += parameters @@ -525,7 +525,7 @@ def dummy_parameter(): # add parameters from template to template_params, replacing parameters with names that # clash with those in the circuit. - for idx, t_idx in enumerate(template_sublist): + for t_idx in template_sublist: node = template_dag_dep.get_node(t_idx) sub_node_params = [] for t_param_exp in node.op.params: @@ -567,6 +567,9 @@ def dummy_parameter(): if isinstance(circuit_param, ParameterExpression): for param in circuit_param.parameters: circ_dict[param] = param.sympify() + elif template_param != circuit_param: + # Both are numeric parameters, but aren't equal. + return None if not temp_symbols: return template_dag_dep @@ -592,9 +595,10 @@ def dummy_parameter(): for param in param_exp.parameters: if param in fake_bind: if fake_bind[param] not in bound_params: - bound_params.append(param_exp.assign(param, fake_bind[param])) + param_exp = param_exp.assign(param, fake_bind[param]) else: - bound_params.append(float(param_exp)) + param_exp = float(param_exp) + bound_params.append(param_exp) node.op.params = bound_params From 94e1884950e63078b62b508c1fe98d899ac9870e Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 24 Mar 2022 22:47:37 +0000 Subject: [PATCH 84/84] Silence overzealous pylint warning --- test/python/transpiler/test_template_matching.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index 81914912583a..803ff50bbded 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -28,7 +28,7 @@ from qiskit.transpiler.exceptions import TranspilerError -def _ry_to_rz_template_pass(parameter=None, extra_costs=None): +def _ry_to_rz_template_pass(parameter: Parameter = None, extra_costs=None): """Create a simple pass manager that runs a template optimisation with a single transformation. It turns ``RX(pi/2).RY(parameter).RX(-pi/2)`` into the equivalent virtual ``RZ`` rotation, where if ``parameter`` is given, it will be the instance used in the template.""" @@ -38,7 +38,7 @@ def _ry_to_rz_template_pass(parameter=None, extra_costs=None): template.rx(-np.pi / 2, 0) template.ry(parameter, 0) template.rx(np.pi / 2, 0) - template.rz(-parameter, 0) + template.rz(-parameter, 0) # pylint: disable=invalid-unary-operand-type costs = {"rx": 16, "ry": 16, "rz": 0} if extra_costs is not None: