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

Conversation

nbronn
Copy link
Contributor

@nbronn nbronn commented Aug 11, 2021

Summary

This PR adds the ability to pass circuits with unbound Parameters into the template optimization transpilation pass. At this time, the .inverse() method used in each gate class is not fully implemented to handle the necessary inversion that may be required in certain templates.

Details and comments

Since Parameters may be used in both templates and circuits, these need to be treated separately so that the template Parameters can be replaced by those from the circuit. These are solved for in template_substitution.py using Sympy, and hence Parameters defined by LaTeX expressions are currently unsupported due to parsing issues.

Currently, only the inverse of the RXGate is supported due to the type-checking cases for ParameterExpressions consisting of Parameters, ParameterExpressions consisting of numbers, and numbers. This issue is probably better handled in a general context instead of doing this type-checking in each gate definition.

Similarly, this PR will not pass the test_unbound_parameters unit test in test_template_matching.py because the inverse gate is defined as a UnitaryGate that is currently cast to a complex numpy array in unitary.py.

nbronn added 25 commits April 18, 2021 21:42
… parameters, removed some excess files from another branch
…ing Parameters either in equations or symbols set in template_substitution.py
… lean on parse_expr from sympy to cast to symbols
… transpilig step followed by parameter binding
@nbronn nbronn requested a review from a team as a code owner August 11, 2021 21:24
@nbronn
Copy link
Contributor Author

nbronn commented Mar 1, 2022

Concerning the main remaining issue here, I fixed the logic so that the binding attempts go through every (assumed) ParameterExpression in node.op.params, then every Parameter in each of those ParameterExpressions is bound if it is in fake_bind (i.e., has a solution from the sympy solver). This should solve the problem with the length of bound_params getting out of hand.

It also works with multiple parameters, and I've added a test test_two_parameter_template to test_template_matching.py that successfully achieves two-parameter template matching.

nbronn and others added 12 commits March 3, 2022 11:53
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.
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.
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.
Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

I think the recent changes had broken some of the core template matching - in my tests, very simple templates were inserting the wrong numeric parameters in the pass. This is really why I've been hammering on the idea of exact tests of the behaviour - I know the test suite was lacking in them before, but they really are crucial.

This PR's been going for a while now, in good part because of slowness on my part and me being away, so I've gone ahead and fixed the bugs I found in this review, added the explicit tests I'd been asking for and improved a couple of the data structures used in the matching. I'll have another look tomorrow before merging to make sure to check my own code, but unless I've made a howler in the test suite, this is hopefully good to merge.

Thanks for sticking with it - it'd be good if you could look over the new tests that I've added to make sure you agree that they all should pass (they currently do). If so, I'll be able to merge it soon.

Comment on lines 25 to 26
if _optionals.HAS_SYMENGINE:
import symengine
Copy link
Member

Choose a reason for hiding this comment

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

Actually symengine isn't that heavy to import as it's mostly cython and a statically linked c++ lib. It's sympy we normally have to avoid as it's super slow to import. The bigger thing is that symengine has a narrower platform support than qiskit and we can't rely on it being present, it's in the requirements list for platforms that support it but not all do.

Copy link
Member

@jakelishman jakelishman Mar 24, 2022

Choose a reason for hiding this comment

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

Oh, it actually appeared quite heavy on my laptop when I tried it - I must have been hitting a really cold cache. At any rate, these changes were unrelated to this PR logically, so I put them back in place.

Comment on lines 515 to 517
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)
Copy link
Member

Choose a reason for hiding this comment

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

Using a string for this isn't ideal: it would falsely detect duplications if the template parameter was called alpha and a circuit parameter was called a, for example. It's also quite inefficient to build up a string like this, but that's probably not the limiting factor.

Instead, it's better to use a set or a dictionary (some form of hashmap), which provides constant-time lookup of individual objects. I've done that in a14dc37.

Comment on lines 530 to 534
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])
Copy link
Member

Choose a reason for hiding this comment

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

This generates a new parameter every time the clash is encountered, which would likely have caused issues if the same parameter needed to be put into a circuit more than once. Better to create the new parameters only once, and retrieve them from a dictionary on subsequent accesses. This can be made quite elegant with collections.defaultdict - I've done that in a14dc37.

Comment on lines 535 to 539
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)
Copy link
Member

Choose a reason for hiding this comment

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

These appends were at the wrong level - they only got added to the lists if the parameters were expressions. That was a problem for template_params in particular, since it would have the wrong number of elements, and the subsequent zip between circuit_params and template_params would line things up incorrectly.

I added very explicit tests to show the failures in fc83156, and fixed them in a14dc37.

Comment on lines 25 to 26
if _optionals.HAS_SYMENGINE:
import symengine
Copy link
Member

Choose a reason for hiding this comment

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

Actually symengine isn't that heavy to import as it's mostly cython and a statically linked c++ lib. It's sympy we normally have to avoid as it's super slow to import. The bigger thing is that symengine has a narrower platform support than qiskit and we can't rely on it being present, it's in the requirements list for platforms that support it but not all do.

@jakelishman jakelishman dismissed their stale review March 24, 2022 22:51

requested changes were made, but still needs a check over

@nbronn
Copy link
Contributor Author

nbronn commented Mar 25, 2022

Thanks @jakelishman! I can confirm that all the tests in test.python.transpiler.test_template_matching.TestTemplateMatching pass on my laptop as well.

@jakelishman jakelishman added Changelog: New Feature Include in the "Added" section of the changelog automerge labels Mar 28, 2022
@jakelishman jakelishman changed the title Added Parameter Expression functions for Template Optimization pass Allow ParameterExpression values in TemplateOptimization pass Mar 28, 2022
@mergify mergify bot merged commit e8c5c94 into Qiskit:main Mar 28, 2022
@nbronn nbronn deleted the template-param-expression branch March 29, 2022 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants