-
Notifications
You must be signed in to change notification settings - Fork 68
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
Changes from 3 commits
81dd32a
8710bbc
3183a7e
5359ce3
ac64638
9917ea5
cad1a5c
2d7cac2
1bc6e5c
2ce3a9d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,7 +24,7 @@ | |
|
||
import numpy as np | ||
from qiskit import QuantumCircuit | ||
from qiskit.circuit import Parameter | ||
from qiskit.circuit import Parameter, ParameterExpression | ||
from qiskit.exceptions import QiskitError | ||
|
||
import pennylane as qml | ||
|
@@ -36,17 +36,19 @@ | |
inv_map = {v.__name__: k for k, v in QISKIT_OPERATION_MAP.items()} | ||
|
||
|
||
def _check_parameter_bound(param): | ||
def _check_parameter_bound(param, var_ref_map): | ||
"""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]): | ||
a dictionary mapping qiskit parameters to PennyLane variables | ||
|
||
Returns: | ||
param: the parameter after the check | ||
qiskit.circuit.Parameter: the parameter after the check | ||
""" | ||
if isinstance(param, Parameter): | ||
if isinstance(param, Parameter) and param not in var_ref_map: | ||
raise ValueError("The parameter {} was not bound correctly.".format(param)) | ||
return param | ||
|
||
|
@@ -59,19 +61,33 @@ def _check_circuit_and_bind_parameters(quantum_circuit: QuantumCircuit, params: | |
params (dict): dictionary of the parameters in the circuit | ||
|
||
Returns: | ||
qc (QuantumCircuit): quantum circuit with bound parameters | ||
QuantumCircuit: quantum circuit with bound parameters | ||
dict[qiskit.circuit.Parameter, pennylane.variable.VariableRef]: a dictionary mapping | ||
qiskit parameters to PennyLane variables | ||
|
||
""" | ||
if not isinstance(quantum_circuit, QuantumCircuit): | ||
raise ValueError("The circuit {} is not a valid Qiskit QuantumCircuit.".format(quantum_circuit)) | ||
|
||
if params is None: | ||
return quantum_circuit | ||
return quantum_circuit, {} | ||
|
||
# map qiskit parameters to PennyLane differentiable VariableRefs. | ||
var_ref_map = {} | ||
|
||
for k, v in params.items(): | ||
if isinstance(v, qml.variable.Variable): | ||
params.update({k: v.val}) | ||
# iterate through the parameters | ||
if isinstance(v, qml.variable.VariableRef): | ||
# If a parameter is a Variable reference, then it represents | ||
# a differentiable parameter. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)} There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 😆 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
var_ref_map[k] = v | ||
|
||
for k in var_ref_map: | ||
# Since we cannot bind VariableRefs to Qiskit circuits, | ||
# we must remove them from the binding dictionary before binding. | ||
del params[k] | ||
|
||
return quantum_circuit.bind_parameters(params) | ||
return quantum_circuit.bind_parameters(params), var_ref_map | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
While in theory, this means four separate functions it might be worth having a separate function that solely extracts the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
|
||
|
||
def map_wires(wires: list, qc_wires: list) -> dict: | ||
|
@@ -83,7 +99,7 @@ def map_wires(wires: list, qc_wires: list) -> dict: | |
qc_wires (list): wires from the converted quantum circuit | ||
|
||
Returns: | ||
wire_map (dict): map from quantum circuit wires to the user defined wires | ||
dict: map from quantum circuit wires to the user defined wires | ||
josh146 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
if wires is None: | ||
return dict(zip(qc_wires, range(len(qc_wires)))) | ||
|
@@ -105,17 +121,12 @@ def execute_supported_operation(operation_name: str, parameters: list, wires: li | |
""" | ||
operation = getattr(pennylane_ops, operation_name) | ||
|
||
parameters = [_check_parameter_bound(param) for param in parameters] | ||
|
||
if not parameters: | ||
operation(wires=wires) | ||
elif operation_name == 'QubitStateVector': | ||
operation(np.array(parameters), wires=wires) | ||
elif operation_name == 'QubitUnitary': | ||
operation(*parameters, wires=wires) | ||
else: | ||
float_params = [float(param) for param in parameters] | ||
operation(*float_params, wires=wires) | ||
operation(*parameters, wires=wires) | ||
|
||
|
||
def load(quantum_circuit: QuantumCircuit): | ||
|
@@ -144,8 +155,7 @@ def _function(params: dict = None, wires: list = None): | |
Returns: | ||
function: the new PennyLane template | ||
""" | ||
|
||
qc = _check_circuit_and_bind_parameters(quantum_circuit, params) | ||
qc, var_ref_map = _check_circuit_and_bind_parameters(quantum_circuit, params) | ||
|
||
# Wires from a qiskit circuit are unique w.r.t. a register name and a qubit index | ||
qc_wires = [(q.register.name, q.index) for q in quantum_circuit.qubits] | ||
|
@@ -160,8 +170,24 @@ def _function(params: dict = None, wires: list = None): | |
operation_wires = [wire_map[(qubit.register.name, qubit.index)] for qubit in op[1]] | ||
|
||
if instruction_name in inv_map and inv_map[instruction_name] in pennylane_ops.ops: | ||
# Extract the bound parameters from the operation. If the bound parameters are a | ||
# Qiskit ParameterExpression, then replace it with the corresponding PennyLane | ||
# variable from the var_ref_map dictionary. | ||
|
||
parameters = [] | ||
for p in op[0].params: | ||
|
||
_check_parameter_bound(p, var_ref_map) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't help but feel we can use the return value of There was a problem hiding this comment. Choose a reason for hiding this commentThe 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))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not too sure about this line here, in particular There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, this is partly because
As a result, Side note: probably out of scope for this PR, but we should create a PR that raises an error if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, got ya! So a
Having seen that, I was also wondering why simply a There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
else: | ||
parameters.append(float(p)) | ||
else: | ||
parameters.append(p) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) |
||
|
||
execute_supported_operation(inv_map[instruction_name], op[0].params, operation_wires) | ||
execute_supported_operation(inv_map[instruction_name], parameters, operation_wires) | ||
|
||
elif instruction_name == 'SdgGate': | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
qiskit>=0.15 | ||
PennyLane>=0.7.0 | ||
git+git://github.com/XanaduAI/pennylane.git#egg=pennylane | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious why this is needed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A recent PR in PennyLane master renamed the |
||
numpy | ||
networkx==2.3 |
There was a problem hiding this comment.
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:There was a problem hiding this comment.
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!