Skip to content

Commit

Permalink
Get local tests passing (#127)
Browse files Browse the repository at this point in the history
* variable to requires_grad, test that gets passed tensor

* bind `requires_grad=False` variables in converter.py

* black

* requirements.txt to master pennylane version

* Update requirements.txt

Co-authored-by: Josh Izaac <[email protected]>

* update changelog

* Revert "black"

This reverts commit 61c1f98.

* removing `Variable` from comments and docstrings

* minor doc change

* black on changed components

* remove variable

Co-authored-by: Josh Izaac <[email protected]>
  • Loading branch information
albi3ro and josh146 authored Feb 2, 2021
1 parent af673fa commit 224f7f4
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 55 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@

### Bug fixes

* When loading Qiskit circuits to PennyLane templates using `load` in `converter.py`, parameters with `requires_grad=False` are bound to the circuit. The old version bound objects that were not PennyLane `Variable`'s, but that object class is now deprecated.
[(#127)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/127)

### Contributors

This release contains contributions from (in alphabetical order):

Christina Lee

---

# Release 0.13.0
Expand Down
36 changes: 17 additions & 19 deletions pennylane_qiskit/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,44 +32,43 @@
inv_map = {v.__name__: k for k, v in QISKIT_OPERATION_MAP.items()}


def _check_parameter_bound(param: Parameter, var_ref_map: Dict[Parameter, qml.variable.Variable]):
def _check_parameter_bound(param: Parameter, var_ref_map: Dict[Parameter, Any]):
"""Utility function determining if a certain parameter in a QuantumCircuit has
been bound.
Args:
param (qiskit.circuit.Parameter): the parameter to be checked
var_ref_map (dict[qiskit.circuit.Parameter, pennylane.variable.Variable]):
a dictionary mapping qiskit parameters to PennyLane variables
var_ref_map (dict[qiskit.circuit.Parameter, Any]):
a dictionary mapping qiskit parameters to trainable parameter values
"""
if isinstance(param, Parameter) and param not in var_ref_map:
raise ValueError("The parameter {} was not bound correctly.".format(param))


def _extract_variable_refs(params: Dict[Parameter, Any]) -> Dict[Parameter, qml.variable.Variable]:
def _extract_variable_refs(params: Dict[Parameter, Any]) -> Dict[Parameter, Any]:
"""Iterate through the parameter mapping to be bound to the circuit,
and return a dictionary containing the differentiable parameters.
and return a dictionary containing the trainable parameters.
Args:
params (dict): dictionary of the parameters in the circuit to their corresponding values
Returns:
dict[qiskit.circuit.Parameter, pennylane.variable.Variable]: a dictionary mapping
qiskit parameters to PennyLane variables
dict[qiskit.circuit.Parameter, Any]: a dictionary mapping
qiskit parameters to trainable parameter values
"""
variable_refs = {}
# map qiskit parameters to PennyLane differentiable Variables.
# map qiskit parameters to PennyLane trainable parameter values
if params is not None:
for k, v in params.items():

# Values can be arrays of size 1, need to extract the Python scalar
# (this can happen e.g. when indexing into a PennyLane numpy array)
if isinstance(v, np.ndarray):
v = v.item()

if isinstance(v, qml.variable.Variable):
if getattr(v, "requires_grad", True):
# Values can be arrays of size 1, need to extract the Python scalar
# (this can happen e.g. when indexing into a PennyLane numpy array)
if isinstance(v, np.ndarray):
v = v.item()
variable_refs[k] = v

return variable_refs # map qiskit parameters to PennyLane differentiable Variables.
return variable_refs # map qiskit parameters to trainable parameter values


def _check_circuit_and_bind_parameters(
Expand All @@ -80,9 +79,8 @@ def _check_circuit_and_bind_parameters(
Args:
quantum_circuit (QuantumCircuit): the quantum circuit to check and bind the parameters for
params (dict): dictionary of the parameters in the circuit to their corresponding values
diff_params (dict): dictionary mapping the differentiable parameters to PennyLane
Variable instances
diff_params (dict): dictionary mapping the differentiable parameters to trainable parameter
values
Returns:
QuantumCircuit: quantum circuit with bound parameters
"""
Expand All @@ -95,7 +93,7 @@ def _check_circuit_and_bind_parameters(
return quantum_circuit

for k in diff_params:
# Since we cannot bind Variables to Qiskit circuits,
# Since we don't bind trainable values to Qiskit circuits,
# we must remove them from the binding dictionary before binding.
del params[k]

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
qiskit>=0.23.1
pennylane>=0.13.0
git+https://github.com/PennyLaneAI/pennylane.git
numpy
networkx>=2.2;python_version>'3.5'
networkx>=2.2,<2.4;python_version=='3.5'
12 changes: 7 additions & 5 deletions tests/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,22 +239,24 @@ def test_invalid_parameter_expression(self, recorder):

with pytest.raises(ValueError, match='PennyLane does not support expressions'):
with recorder:
quantum_circuit(params={theta: qml.variable.Variable(0), phi: qml.variable.Variable(1)})
quantum_circuit(params={theta: 0, phi: 1})

def test_extra_parameters_were_passed(self, recorder):
"""Tests that loading raises an error when extra parameters were
passed."""

theta = Parameter('θ')
phi = Parameter('φ')
x = np.tensor(0.5, requires_grad=False)
y = np.tensor(0.3, requires_grad=False)

qc = QuantumCircuit(3, 1)

quantum_circuit = load(qc)

with pytest.raises(QiskitError):
with recorder:
quantum_circuit(params={theta: 0.5, phi: 0.3})
quantum_circuit(params={theta: x, phi: y})

@pytest.mark.parametrize("qiskit_operation, pennylane_name", [(QuantumCircuit.crx, "CRX"), (QuantumCircuit.crz, "CRZ"), (QuantumCircuit.cry, "CRY")])
def test_controlled_rotations(self, qiskit_operation, pennylane_name, recorder):
Expand Down Expand Up @@ -588,8 +590,8 @@ def test_quantum_circuit_error_by_passing_wrong_parameters(self, recorder):
"""Tests the load method for a QuantumCircuit raises a QiskitError,
if the wrong type of arguments were passed."""

theta = Parameter('θ')
angle = 'some_string_instead_of_an_angle'
theta = Parameter("θ")
angle = np.tensor("some_string_instead_of_an_angle", requires_grad=False)

qc = QuantumCircuit(3, 1)
qc.rz(theta, [0])
Expand All @@ -605,7 +607,7 @@ def test_quantum_circuit_error_passing_parameters_not_required(self, recorder):
that are not required were passed."""

theta = Parameter('θ')
angle = 0.5
angle = np.tensor(0.5, requires_grad=False)

qc = QuantumCircuit(3, 1)
qc.z([0])
Expand Down
39 changes: 9 additions & 30 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ def circuit(phi=None):
circuit(phi=phi)

# Check parameters
assert all([isinstance(x, float) for x in lst])
assert all([isinstance(x, tensor) for x in lst])

def test_tensor_unwrapped_gradient_no_error(self, monkeypatch):
"""Tests that the gradient calculation of a circuit that contains a
Expand Down Expand Up @@ -391,28 +391,11 @@ def circuit(phi=None):

for i in range(phi.shape[1]):
# Test each rotation applied
assert rec.queue[0].name == "RX"
assert len(rec.queue[0].parameters) == 1
assert rec.queue[i].name == "RX"
assert len(rec.queue[i].parameters) == 1

# Test that the gate parameter is not a PennyLane tensor, but a
# float
assert not isinstance(rec.queue[0].parameters[0], tensor)
assert isinstance(rec.queue[0].parameters[0], float)

def test_multiple_gate_parameter(self):
"""Test that a QNode handles passing multiple tensor objects to the
same gate without an error"""
dev = qml.device("qiskit.aer", wires=1)

@qml.qnode(dev)
def circuit(phi=None):
for idx, x in enumerate(phi):
qml.Rot(*x, wires=idx)
return qml.expval(qml.PauliZ(0))

phi = tensor([[0.04439891, 0.14490549, 3.29725643]])

circuit(phi=phi)
# Test that the gate parameter is a PennyLane tensor
assert isinstance(rec.queue[i].parameters[0], tensor)

def test_multiple_gate_parameter(self):
"""Test that when supplied a PennyLane tensor, a QNode passes arguments
Expand All @@ -435,16 +418,12 @@ def circuit(phi=None):
assert rec.queue[0].name == "Rot"
assert len(rec.queue[0].parameters) == 3

# Test that the gate parameters are not PennyLane tensors,
# but are instead floats
assert not isinstance(rec.queue[0].parameters[0], tensor)
assert isinstance(rec.queue[0].parameters[0], float)
# Test that the gate parameters are PennyLane tensors,
assert isinstance(rec.queue[0].parameters[0], tensor)

assert not isinstance(rec.queue[0].parameters[1], tensor)
assert isinstance(rec.queue[0].parameters[1], float)
assert isinstance(rec.queue[0].parameters[1], tensor)

assert not isinstance(rec.queue[0].parameters[2], tensor)
assert isinstance(rec.queue[0].parameters[2], float)
assert isinstance(rec.queue[0].parameters[2], tensor)

class TestInverses:
"""Integration tests checking that the inverse of the operations are applied."""
Expand Down

0 comments on commit 224f7f4

Please sign in to comment.