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

Fixed bug in gradients of loaded qiskit circuits #71

Merged
merged 10 commits into from
Feb 28, 2020
Merged

Conversation

josh146
Copy link
Member

@josh146 josh146 commented Feb 24, 2020

Context: By expliciting calling params.val, the VariableRef classes, which allow us to keep track of which operations are using which parameters so that the parameter shift rule can be applied, are being lost in the conversion.

Description of the changes:

  • The binding logic in the conversion functions has been modified --- now, a mapping is kept of the differentiable Qiskit parameters to the PennyLane variables. The PennyLane variables are then used for operation execution where needed.

  • An integration test has been added to ensure that the gradient computation works correctly on converted qiskit circuits.

@josh146 josh146 added the bug Something isn't working label Feb 24, 2020
@josh146 josh146 requested a review from antalszava February 24, 2020 15:02
@codecov
Copy link

codecov bot commented Feb 24, 2020

Codecov Report

Merging #71 into master will increase coverage by 0.02%.
The diff coverage is n/a.

@@            Coverage Diff             @@
##           master      #71      +/-   ##
==========================================
+ Coverage   99.08%   99.11%   +0.02%     
==========================================
  Files           7        7              
  Lines         327      338      +11     
==========================================
+ Hits          324      335      +11     
  Misses          3        3              

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 803305a...2ce3a9d. Read the comment docs.

# iterate through the parameters
if isinstance(v, qml.variable.VariableRef):
# If a parameter is a Variable reference, then it represents
# a differentiable parameter.
Copy link
Contributor

Choose a reason for hiding this comment

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

This could go as a dictionary comprehension as well (though might be too long of a one-liner):

var_ref_map = {k:v for k, v in params.items() if isinstance(v, qml.variable.VariableRef)}

Copy link
Member Author

Choose a reason for hiding this comment

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

Will change, I left it as a for loop in an attempt to minimize line changes 😆

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!


return quantum_circuit.bind_parameters(params)
return quantum_circuit.bind_parameters(params), var_ref_map
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be slightly unintuitive that here there's a tuple that is returned (but this is also connected to the original implementation which was my doing 😅 ). Given the change there seem to be four parts of logic here inside of this single function:

  • check_circuit
  • extract VariableRefs
  • remove VariableRefs from params
  • bind parameters

While in theory, this means four separate functions it might be worth having a separate function that solely extracts the VariableRefs and then later the bind_parameters function could delete the needed refs. To me, this latter separation would make this part easier to parse :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!


if isinstance(p, ParameterExpression):
if p.parameters:
parameters.append(var_ref_map.get(min(p.parameters)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Not too sure about this line here, in particular min(p.parameters). How come such a call is made? Perhaps naming a certain part of this line (e.g. min_param=min(p.parameters)) and changing this line accordingly could help with providing further clues to the reader :)

Choose a reason for hiding this comment

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

Does len(p.parameters) == 1? If not, what happens to p's other parameters?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, this is partly because

  • Python doesn't provide a way to index into a set, and

  • Due to a PennyLane restriction, the Qiskit ParameterExpression must always be a set containing a single parameter.

As a result, min(single_element_set) is a useful way of extracting the element. Another approach is single_element_set.pop(), but I tend not to favour pop due to its side effects.

Side note: probably out of scope for this PR, but we should create a PR that raises an error if len(p.parameters) > 1.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, got ya! So a ParameterExpression contains a

Mapping of Parameter instances to the sympy.Symbol serving as their placeholder in expr.

Having seen that, I was also wondering why simply a Parameter could not do it here, but this now allows for ParameterExpression instances that contain only one Parameter as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep - the issue seems to be that even if you start off with a single Parameter, after binding Qiskit converts everything into a ParameterExpression. So Parameter objects never appear at this point in the logic.

Copy link
Contributor

@antalszava antalszava left a comment

Choose a reason for hiding this comment

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

This is amazing @josh146, thank you very much for getting that! I've left a couple of comments mainly for improving my own understanding 🙂

tests/test_converter.py Outdated Show resolved Hide resolved
tests/test_converter.py Outdated Show resolved Hide resolved
Copy link

@speller26 speller26 left a comment

Choose a reason for hiding this comment

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

Thanks for getting to this so quickly!

Comment on lines 180 to 188
_check_parameter_bound(p, var_ref_map)

if isinstance(p, ParameterExpression):
if p.parameters:
parameters.append(var_ref_map.get(min(p.parameters)))
else:
parameters.append(float(p))
else:
parameters.append(p)

Choose a reason for hiding this comment

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

Don't feel particularly strongly about this, but this could be pulled into a helper method, so parameters then gets constructed as a list comprehension.

Copy link
Member Author

Choose a reason for hiding this comment

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

I had originally had this as a list comprehension, but changed it to a loop for readability (it became quite long due to the ternary expressions)

requirements.txt Outdated
@@ -1,4 +1,4 @@
qiskit>=0.15
PennyLane>=0.7.0
git+git://github.com/XanaduAI/pennylane.git#egg=pennylane

Choose a reason for hiding this comment

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

Curious why this is needed

Copy link
Member Author

Choose a reason for hiding this comment

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

A recent PR in PennyLane master renamed the Variable class to VariableRef --- I wanted to double check that this PR passes against the latest master of PennyLane. However, this renaming will likely be reverted due to side effects that occurred, so this change here is temporary and should be reverted before this PR is merged.

"""Utility function determining if a certain parameter in a QuantumCircuit has
been bound.

Args:
param: the parameter to be checked
param (qiskit.circuit.Parameter): the parameter to be checked
var_ref_map (dict[qiskit.circuit.Parameter, pennylane.variable.VariableRef]):

Choose a reason for hiding this comment

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

At this point, it might be worth considering the typing module:

from typing import Dict

def _check_parameter_bound(param: ..., var_ref_map: Dict[Parameter, VariableRef]) -> Parameter:
    """
    ...
    var_ref_map (Dict[Parameter, VariableRef]): ...
    """
    ...

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea, I will add that in!

parameters = []
for p in op[0].params:

_check_parameter_bound(p, var_ref_map)

Choose a reason for hiding this comment

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

Can't help but feel we can use the return value of _check_parameter_bound

Copy link
Member Author

Choose a reason for hiding this comment

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

I've removed the return statement on _check_parameter_bound, since it is never used


if isinstance(p, ParameterExpression):
if p.parameters:
parameters.append(var_ref_map.get(min(p.parameters)))

Choose a reason for hiding this comment

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

Does len(p.parameters) == 1? If not, what happens to p's other parameters?

@josh146 josh146 requested a review from antalszava February 25, 2020 10:44
Copy link
Contributor

@antalszava antalszava left a comment

Choose a reason for hiding this comment

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

Looks good to me @josh146! 😊

@co9olguy
Copy link
Member

Ready to merge @josh146?

@josh146
Copy link
Member Author

josh146 commented Feb 26, 2020

I'm holding off, as I would like to revert VariableRef -> Variable in PennyLane master first, to avoid updating all the other plugins as well.

@josh146 josh146 merged commit 016fbc1 into master Feb 28, 2020
@josh146 josh146 deleted the bug/load-grad-fix branch February 28, 2020 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants