Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ParameterExpression values in TemplateOptimization pass #6899

Merged
merged 142 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from 114 commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
32ee4db
added new equivalence rzx_zz4
nbronn Apr 19, 2021
8cfc6d9
added another zz-ish template (looks for basis gates)
nbronn Apr 27, 2021
c063402
Merge branch 'master' of github.com:Qiskit/qiskit-terra into template…
nbronn Apr 27, 2021
1d290a4
put the rzx_templates in
nbronn Jun 24, 2021
ad85838
looking at adding additions to rzx templates
nbronn Jul 1, 2021
7098e4f
fixes parsing of Parameter name for sympy during template optimization
nbronn Jul 2, 2021
3e23380
Merge branch 'main' of github.com:nbronn/qiskit-terra into template-p…
nbronn Jul 2, 2021
6e18a54
Merge branch 'main' into template-param-expression
nbronn Jul 2, 2021
c7605d7
generalized fix for multiple parameters in an expression and constant…
nbronn Jul 6, 2021
ed74ccf
one more minor fix to bring inline with qiskit-terra main
nbronn Jul 6, 2021
e308b84
trying to fix parameter parsing for template optimization
nbronn Jul 8, 2021
8fc1fa6
made sure floats were added correctly to (template) parameters
nbronn Jul 9, 2021
78d3339
got template matching to work with floats, sympy still not understand…
nbronn Jul 9, 2021
cc1a55a
Merge branch 'main' of github.com:Qiskit/qiskit-terra into template-p…
nbronn Jul 9, 2021
732e887
further modifications to accept circuit parameters for template optim…
nbronn Jul 12, 2021
0c63ca2
debugging the binding of template parameters
nbronn Jul 21, 2021
6a18dd1
realized I was not treating ParameterExpressions carefully and should…
nbronn Jul 22, 2021
133171c
converted all bindings to ParameterExpressions (via .assign()), set t…
nbronn Jul 25, 2021
caee274
cleaned up _attempt_bind routine
nbronn Jul 26, 2021
bd67056
exploring overriding removing the matched scenarios with parameters
nbronn Jul 28, 2021
38e8bde
introduced a total hack for dealing with ParamterExpressions that con…
nbronn Jul 30, 2021
a99726a
(non-latex) parameters now currently working in template optimization…
nbronn Aug 10, 2021
59009db
cleaned up some whitespace and removed commented-out lines
nbronn Aug 10, 2021
432bb3f
cleaned up some tox/lint errors
nbronn Aug 11, 2021
b308587
removed unneccessary Parameter import
nbronn Aug 11, 2021
5fd736a
Merge branch 'main' into template-param-expression
nbronn Aug 11, 2021
71aa17e
bypassed unit test test_unbound_parameters() and re-tox/lint
nbronn Aug 16, 2021
5c3a62b
Merge branch 'main' of github.com:Qiskit/qiskit-terra into template-p…
nbronn Aug 16, 2021
9f373e9
Merge branch 'template-param-expression' of github.com:nbronn/qiskit-…
nbronn Aug 16, 2021
bf590d3
fixed one last linting issue
nbronn Aug 16, 2021
0f5f0d6
fixed cyclic import error
nbronn Aug 17, 2021
4f5b69e
Merge branch 'main' into template-param-expression
nbronn Aug 17, 2021
bd3f9ed
modified calibration_creator to accept a ParameterExpression containi…
nbronn Aug 17, 2021
6c9831c
Merge branch 'template-param-expression' of github.com:nbronn/qiskit-…
nbronn Aug 17, 2021
d9530e6
fixed an mismatch when trying to add calibrations and addressed a con…
nbronn Aug 25, 2021
06b9cf1
Merge branch 'main' into template-param-expression
nbronn Aug 25, 2021
a9e7cdf
last tox/lint checks i'm sure ;)
nbronn Aug 25, 2021
4ea8ff5
moved try/except from scheduling to calibration
nbronn Aug 31, 2021
443a6c9
now params comes from node_op argument
nbronn Aug 31, 2021
04dfc37
Merge branch 'main' of github.com:Qiskit/qiskit-terra into template-p…
nbronn Sep 3, 2021
168c3cc
handling error in case gate parameters is empty in dagcircuit.py
nbronn Sep 3, 2021
0e9c39f
Merge branch 'main' of github.com:Qiskit/qiskit-terra into template-p…
nbronn Sep 17, 2021
d7b55b3
Merge branch 'main' into template-param-expression
nbronn Sep 22, 2021
0547f53
Merge branch 'main' into template-param-expression
nbronn Sep 29, 2021
fa51c3c
Fix template matching for parameters with LaTeX name.
rafal-pracht Oct 5, 2021
d4044a0
Merge pull request #1 from rafal-pracht/template-param-expression
nbronn Oct 6, 2021
19007b7
Merge branch 'main' into template-param-expression
nbronn Oct 6, 2021
d6e1cd7
Merge branch 'main' into template-param-expression
nbronn Oct 20, 2021
2065826
added missing docstring
nbronn Oct 20, 2021
a8b9b05
removed pdb set_trace
nbronn Oct 20, 2021
72990f5
Merge branch 'main' into template-param-expression
nbronn Oct 22, 2021
d177d84
Merge branch 'main' into template-param-expression
nbronn Oct 22, 2021
bd6dda4
made changes requested in PR 6899
nbronn Nov 2, 2021
6ea9d90
made changes requested in PR 6899 #2
nbronn Nov 2, 2021
592963b
Merge branch 'main' into template-param-expression
nbronn Nov 2, 2021
cebce74
remembered to tighten try/except handling
nbronn Nov 2, 2021
4e2291c
Merge branch 'template-param-expression' of github.com:nbronn/qiskit-…
nbronn Nov 2, 2021
6ccf293
Merge branch 'main' into template-param-expression
nbronn Nov 2, 2021
8b6bf37
finished making changes requested in PR 6899
nbronn Nov 3, 2021
fdffda6
Merge branch 'main' into template-param-expression
nbronn Nov 3, 2021
7721f23
fixed remaining linting issue
nbronn Nov 3, 2021
9130a2a
Merge branch 'main' into template-param-expression
nbronn Nov 3, 2021
5e31398
added comment about templates working for parameterized RZXGates and …
nbronn Nov 3, 2021
eb8ba0d
Merge branch 'template-param-expression' of github.com:nbronn/qiskit-…
nbronn Nov 3, 2021
a92b942
Merge branch 'main' into template-param-expression
nbronn Nov 4, 2021
84d65df
Merge branch 'main' into template-param-expression
nbronn Nov 5, 2021
17d5002
Merge branch 'main' into template-param-expression
nbronn Nov 9, 2021
3db29f0
Merge branch 'main' into template-param-expression
nbronn Nov 11, 2021
31b3d62
Merge branch 'main' into template-param-expression
nbronn Nov 12, 2021
1e8b009
Merge branch 'main' into template-param-expression
nbronn Nov 16, 2021
bdee233
Merge branch 'main' into template-param-expression
nbronn Nov 16, 2021
6d419de
Merge branch 'main' into template-param-expression
nbronn Nov 16, 2021
b362089
Merge branch 'main' into template-param-expression
nbronn Nov 18, 2021
2973339
Fix test unbound parameters
rafal-pracht Nov 19, 2021
bb1c797
Check if matrix with Parameter is unitary
rafal-pracht Nov 23, 2021
d531d95
Merge branch 'template-param-expression' into template-param-expression
rafal-pracht Nov 23, 2021
30706cd
Merge branch 'main' of github.com:Qiskit/qiskit-terra into template-p…
nbronn Nov 23, 2021
9260cf4
Fix merge issue
rafal-pracht Nov 24, 2021
767ccf4
Merge pull request #4 from rafal-pracht/template-param-expression
nbronn Nov 24, 2021
28b384f
Merge branch 'main' into template-param-expression
nbronn Dec 15, 2021
d55e75c
Merge branch 'template-param-expression' of github.com:nbronn/qiskit-…
nbronn Dec 15, 2021
f50c4e6
removed real=True in two symbol Symbol expressions, which was messing…
nbronn Dec 16, 2021
e32d038
generalized to iterate over parameters, and removed reference to priv…
nbronn Dec 16, 2021
c21fdf1
modified .get_sympy_expr() to use symengine if possible
nbronn Dec 16, 2021
bc42afb
made the negation of the RXGate() much less verbose
nbronn Dec 16, 2021
cce7b65
working thru tox/lint checks
nbronn Dec 16, 2021
5506200
added unit test test_unbound_parameters_in_rzx_templates to confirm t…
nbronn Dec 16, 2021
708bfcc
Fix unbund parameters test
rafal-pracht Dec 17, 2021
84d566b
Merge pull request #6 from rafal-pracht/template-param-expression
nbronn Dec 20, 2021
54403b0
fixed issue with adding calibrations without params
nbronn Dec 20, 2021
3c15da0
Merge branch 'main' into template-param-expression
nbronn Dec 20, 2021
9355893
Add real=True to symbols
rafal-pracht Dec 20, 2021
7e9f8d9
fixed linting issue
nbronn Dec 20, 2021
0a179b6
Fix for symengine
rafal-pracht Dec 20, 2021
5f86049
simplified the parameter handling for adding calibrations to gates
nbronn Dec 20, 2021
0835763
Merge pull request #7 from rafal-pracht/template-param-expression
nbronn Dec 20, 2021
680ec53
added a check for unitary on an arbitrary float in the case symengine…
nbronn Dec 20, 2021
f276cd7
Parammeter can be complex
rafal-pracht Dec 29, 2021
a295831
Merge pull request #8 from rafal-pracht/template-param-expression
nbronn Jan 3, 2022
2f244bc
Merge branch 'main' into template-param-expression
nbronn Jan 3, 2022
0434a70
Merge branch 'template-param-expression' of github.com:nbronn/qiskit-…
nbronn Jan 3, 2022
055832d
fixed tox/lint issues
nbronn Jan 4, 2022
2504c2e
removed one more imposition of real parameters
nbronn Jan 5, 2022
0f1cf5d
one last linting issue
nbronn Jan 5, 2022
c672ee3
Merge branch 'main' into template-param-expression
nbronn Jan 26, 2022
9a94bb2
modified release notes
nbronn Jan 26, 2022
48173c1
fixed some transpiler library imports that were out of date
nbronn Jan 27, 2022
24097b5
added sphinx referencing to release notes and print statement for the…
nbronn Jan 27, 2022
2f0203c
fixed some tox/lint issues
nbronn Jan 27, 2022
591763a
Fix review issues
rafal-pracht Jan 28, 2022
4dcf014
Merge branch 'main' into template-param-expression
nbronn Feb 1, 2022
8d1bc2e
Merge pull request #10 from rafal-pracht/template-param-expression
nbronn Feb 1, 2022
4f76b73
Merge branch 'template-param-expression' of github.com:nbronn/qiskit-…
nbronn Feb 2, 2022
c8a08e9
fixing last tox/lint issues
nbronn Feb 2, 2022
ee081e3
looking into tox/lint issues
nbronn Feb 11, 2022
3f740fb
added release notes and fixed tox/lint issues
nbronn Feb 11, 2022
69b8472
added method in template_substitution to compare the number of parame…
nbronn Feb 17, 2022
58c8ccf
Merge branch 'main' of github.com:Qiskit/qiskit-terra into template-p…
nbronn Feb 17, 2022
e09b1e3
fixing up some template matching unit tests
nbronn Feb 17, 2022
f01a671
fixed up template matching unit tests to remove calls to UnitaryGate
nbronn Feb 17, 2022
78a2f47
Update qiskit/dagcircuit/dagcircuit.py
nbronn Feb 23, 2022
8496a8c
Update qiskit/extensions/unitary.py
nbronn Feb 24, 2022
82470fd
Update qiskit/extensions/unitary.py
nbronn Feb 24, 2022
1f17aff
added template test with two parameters and new logic in the case the…
nbronn Feb 27, 2022
fb8ca5c
added two-parameter unit test and a check for overlapping parameters …
nbronn Mar 1, 2022
0ce2fa1
Merge branch 'main' of github.com:Qiskit/qiskit-terra into template-p…
nbronn Mar 1, 2022
9f59e06
Merge branch 'template-param-expression' of github.com:nbronn/qiskit-…
nbronn Mar 1, 2022
5de0384
remove ParameterTypeeException from exceptions.py
nbronn Mar 3, 2022
7906036
Restore lazy symengine imports
jakelishman Mar 24, 2022
092f92b
Rename to_sympify_expression to sympify
jakelishman Mar 24, 2022
95dbcfb
Revert now-unnecessary changes to calibration builder
jakelishman Mar 24, 2022
908f87d
Fixup release note
jakelishman Mar 24, 2022
fc83156
Add explicit tests of template matching pass
jakelishman Mar 24, 2022
a14dc37
Fix template parameter substitution
jakelishman Mar 24, 2022
9d07849
Merge remote-tracking branch 'ibm/main' into template-param-expression
jakelishman Mar 24, 2022
7034789
Fix overlooked documentation comment
jakelishman Mar 24, 2022
16bbffb
Remove qasm.pi import in favour of numpy
jakelishman Mar 24, 2022
317e2a6
Add tests of multi-parameter instructions
jakelishman Mar 24, 2022
6ccc663
Fix template matching with multiparameter expressions
jakelishman Mar 24, 2022
94e1884
Silence overzealous pylint warning
jakelishman Mar 24, 2022
87e5fe8
Merge remote-tracking branch 'ibm/main' into template-param-expression
jakelishman Mar 24, 2022
df9c3d3
Merge branch 'main' into template-param-expression
mergify[bot] Mar 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions qiskit/circuit/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
nbronn marked this conversation as resolved.
Show resolved Hide resolved
15 changes: 12 additions & 3 deletions qiskit/circuit/parameterexpression.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import numpy

from qiskit.circuit.exceptions import CircuitError
from qiskit.circuit.exceptions import CircuitError, ParameterTypeError

try:
import symengine
Expand Down Expand Up @@ -434,7 +434,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(
nbronn marked this conversation as resolved.
Show resolved Hide resolved
"ParameterExpression with unbound parameters ({}) "
"cannot be cast to a complex.".format(self.parameters)
) from exc
Expand All @@ -444,7 +444,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
Expand Down Expand Up @@ -532,6 +532,15 @@ def is_real(self):
return False
return True

def to_simplify_expression(self):
"""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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to sympify the expression here? Isn't it already of the relevant type?

I'm not keen on the name of the function (or its existence at all, to be honest, but looking at the existing transpiler pass, the ship has already sailed on that). Perhaps it could be to_sympy_expression - I think we should avoid abbreviations where possible (it's ParameterExpression not ParamExpr, for example), and while we can't always overhaul old code, it'd be good to try and move in that direction going forwards. We use to_ rather than get_ elsewhere in Terra, so that's better for consistency. It's slightly confusing to me that we'd call it sympy when it might not return a sympy object, but I can't think of a better name.

Please can you expand the documentation to explain when it returns each type, and put in a note that this should not be used with general Qiskit functionality; it's for interoperability only, and most Qiskit functions will not accept raw sympy/symengine objects.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to 'to_simplify_expression', this function returns the simplified version of the expression.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What simplification is being performed here? sympify doesn't simplify anything:

In [1]: import sympy
   ...: a, b, c = sympy.symbols("a b c")
   ...: expr = a*(b + c) - a*b - a*c
   ...: expr, sympy.sympify(expr), sympy.simplify(expr)
Out[1]: (-a*b - a*c + a*(b + c), -a*b - a*c + a*(b + c), 0)

If this function is meant to simplify its internals, then it should return ParameterExpression. If it's meant to return a sympy expression, it should be called something like to_sympy_expression with documentation about the fact that it will return a symengine type if available, and it shouldn't simplify things - that would be up to the user.



# Redefine the type so external imports get an evaluated reference; Sphinx needs this to understand
# the type hints.
Expand Down
1 change: 1 addition & 0 deletions qiskit/dagcircuit/dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ def add_calibration(self, gate, qubits, schedule, params=None):
Raises:
Exception: if the gate is of type string and params is None.
"""

nbronn marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(gate, Gate):
self._calibrations[gate.name][
(tuple(qubits), tuple(float(p) for p in gate.params))
Expand Down
20 changes: 15 additions & 5 deletions qiskit/extensions/unitary.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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.exceptions import ParameterTypeError

_DECOMPOSER1Q = OneQubitEulerDecomposer("U3")

Expand All @@ -46,6 +47,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
Expand All @@ -56,11 +58,19 @@ 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.")

nbronn marked this conversation as resolved.
Show resolved Hide resolved
# 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

nbronn marked this conversation as resolved.
Show resolved Hide resolved
nbronn marked this conversation as resolved.
Show resolved Hide resolved
# Check input is N-qubit matrix
input_dim, output_dim = data.shape
num_qubits = int(numpy.log2(input_dim))
Expand Down
15 changes: 12 additions & 3 deletions qiskit/transpiler/passes/calibration/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -224,10 +225,18 @@ 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 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.
"""
theta = node_op.params[0]
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

q1, q2 = qubits[0], qubits[1]

if not self._inst_map.has("cx", qubits):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -500,39 +495,45 @@ 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.append(sym.Eq(parse_expr(str(params)), circuit_params[t_idx]))
for param in params.parameters:
symbols.add(param)

if not symbols:
equations, circ_dict, temp_symbols, sol, fake_bind = [], {}, {}, {}, {}
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_param))
equations.append(sym.Eq(template_param.to_simplify_expression(), circ_param_sym))

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

# Check compatibility by solving the resulting equation
sym_sol = sym.solve(equations)
sym_sol = sym.solve(equations, set(temp_symbols.values()))
jakelishman marked this conversation as resolved.
Show resolved Hide resolved
for key in sym_sol:
try:
sol[str(key)] = float(sym_sol[key])
sol[str(key)] = ParameterExpression(circ_dict, sym_sol[key])
except TypeError:
return None

if not sol:
return None

for param in 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 = []

for param in node.op.params:
if isinstance(param, ParameterExpression):
try:
bound_params.append(float(param.bind(fake_bind)))
except KeyError:
return None
for key in fake_bind:
bound_params.append(param.assign(key, fake_bind[key]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for key, value in fake_bind.items():, but also, isn't this going to add a whole lot of partially bound parameters to the bound_params list? If fake_bind has more than one item in it, this is going to be called a whole lot of times, and there'll be more items in bound_params than there originally were in node.op.params.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the issue here is that we cannot pass bound_params to .bind() because of the requirement the bound values are numeric, whereas we cannot pass bound_params directly to .assign() because it takes parameter and value separately as arguments. Any obvious solution seems to require modification of how .bind() or .assign() work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this comment matches up with my issue (also the point about iterating through a dictionary with .items() still stands). I think this code is buggy, and it's just luckily not triggered.

The problem is that if fake_bind has n elements in it, the length of bound_params would be n * len(params), which is incorrect. Logically, this code is trying take param, and bind all instances of Parameter within it to a corresponding value in fake_bind, and return a single value, but instead it adds lots of values to bound_params. This only works because the templates you're considering only have a single Parameter in them. If we had a template that had two parameters in it, this would be broken. It's quite possible it was broken before as well, but this breaks it in a new way.

Please can you add a test case that involves a template (arbitrary - it can just be invented for the test) that has two parameters, and test that it binds correctly to input circuits with either numeric values or parameter values? For example, here's a (nonsensical) template match that fails with a KeyError, even though it should bind just fine:

In [5]: from qiskit.circuit import Parameter, QuantumCircuit
   ...: from qiskit.transpiler import PassManager
   ...: from qiskit.transpiler.passes import TemplateOptimization, RZXCalibrationBuilder
   ...:
   ...: a = Parameter('a')
   ...: b = Parameter('b')
   ...:
   ...: qc = QuantumCircuit(2)
   ...: qc.p(0.1, 0)
   ...: qc.p(-0.2, 1)
   ...:
   ...: template = QuantumCircuit(2)
   ...: template.p(a, 0)
   ...: template.p(b, 1)
   ...: template.cx(0, 1)
   ...:
   ...: pass_ = TemplateOptimization(
   ...:     user_cost_dict={"cx": 0, "p": 0},
   ...:     template_list=[template],
   ...: )
   ...: PassManager(pass_).run(qc).draw()

There are two issues at play: one is the issue with the length of bound_params, which is new in this PR, and the other is that the calls to ParameterExpression.assign during the template match should assign all parameters in that expression in one go, and only those parameters (this issue I think was pre-existing).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it really isn't possible to have it work for multiple parameters in the template circuit, then this should at least enforce that the template circuit only has a single unbound parameter in it, and the pass should loudly fail if it's given a template with more than one parameter.

else:
bound_params.append(param)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
features:
- |
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::

from qiskit import QuantumCircuit, transpile, schedule
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
nbronn marked this conversation as resolved.
Show resolved Hide resolved

from qiskit.test.mock import FakeCasablanca
backend = FakeCasablanca()

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.rzx_templates(['zz2']))
qc_cz = PassManager(pass_).run(qc)
print('ZX based circuit:')
print(qc_cz)

# Add the calibrations
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 :class:`.Parameter`\s/:class:`.ParameterExpression`\s
must be bound before the :class:`.RZXCalibrationBuilder` can define
calibrations for the :class:`.RZXGate`\ s.
22 changes: 19 additions & 3 deletions test/python/transpiler/test_template_matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -353,9 +354,24 @@ 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this was pre-existing, but it'd be good to make this a test of the entire structure of the circuit, not just an op-count - as I understand it, this test should now return an empty circuit, so we should test that.

(As an aside: it maybe would be good to update the test slightly so it doesn't return a fully empty circuit, because a buggy feature completely wiping out all data in the circuit seems like a plausible failure mode.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was attempting to improve this test by using numpy.testing.assert_allclose and passing it the circuit_out with bound parameters through the Operator class from qiskit.quantum_info, and ran right into an issue I raised dealing with Parameters not being removed from the parameter table. Since this is not yet fixed, I do not think there is a good way to solve this currently.

Perhaps we could move this point to another Issue?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked more, and this test is vitally important, and this PR breaks it as it is. The output of the test should contain 2 CX gates. If you run this test with the changes in this PR, you get

In [4]: circuit_out.draw()
Out[4]:
     ┌────┐
q_0: ┤0   ├─────────────
     │  P │┌───────────┐
q_1: ┤1   ├┤ P(-1.0*β) ├
     └────┘└───────────┘

Note that it's replaced the two CXs with two gates whose parameters don't mean anything. This is a failure; the pass should not have replaced anything, even with your changes. If the test had stronger assertions (like I was suggesting in the previous comment), it would have been clearer that this was a true test failure.

Fortunately, now that I understand this, we can hugely simplify the requirements on UnitaryGate; we can rewrite the test to avoid it by just using a gate that can handle Parameter in its inverse. Here's a clearer version of the test that's exactly equivalent:

    def test_optimizer_does_not_replace_unbound_partial_match(self):
        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},
        )

        circuit_out = PassManager(pass_).run(circuit_in)

        # 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)

If we change the test to that, it lets us revert all the changes to UnitaryGate that I now see are unrelated (sorry again about that), and the test is both clearer and stricter.

The trick, though, is that I think to achieve your goals in the pass, you're going to need to update your logic a bit. You need to keep track of which parameters are dummy parameters used in the template, and which are parameters that come from the circuit. For example, in this test, the beta should not be allowed to be output by the transpiler pass, but in the test you added after this one, the phi should be output (as you have it).

There's more nuance than just that as well, though, because the pass needs to handle the case that a template uses a parameter with the same name as an input circuit without breaking. You can likely handle that bit by re-binding the variables in a template to auto-generated unique names if you detect issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great point to eliminate the UnitaryGate from the unit test entirely (I implemented the above, and also modified test_parametric_template similarly). The test_unbound_parameters_in_rzx_template test is now working so that circuit_in and circuit_out are verified to not be equal (because the template optimization has changed the circuit), yet the operators are equivalent after binding phi to a float.

In order to guarantee template optimization does not increase the number of Parameters in a circuit, there is a new private method _incr_num_parameters in template_substitution.py that counts the number of Parameters in the template and in the circuit and returns true if the former is greater than the latter. If this is true, logic inside _substitution will continue out of the method, preventing the template from being substituted.

As for Parameters in circuits and templates that have the same name, I'm not sure I know how to auto-generate unique names. Trying this out on the test notebook, all the templates I use are of the rzx variety, and the specific Parameter used is always

theta = Parameter("ϴ") # used in all rzx templates

which does not match with

theta = Parameter('θ') # typed from my "Greek" keyboard

or

theta = Parameter('$\\theta$') # maybe the safest, LaTeX version

so it seems that this is not much of a problem currently. (Of course template optimization fails if I use the exact form of theta from the template).


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)
nbronn marked this conversation as resolved.
Show resolved Hide resolved


if __name__ == "__main__":
Expand Down