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

Qubitunitary causes bug with complex matrix #541

Open
co9olguy opened this issue Mar 10, 2020 · 5 comments
Open

Qubitunitary causes bug with complex matrix #541

co9olguy opened this issue Mar 10, 2020 · 5 comments
Labels

Comments

@co9olguy
Copy link
Member

@co9olguy co9olguy commented Mar 10, 2020

Issue description

Issue posted by user max on the forums

  • Expected behavior: The following circuit containing QubitUnitary should function without errors:
dev = qml.device('default.qubit', wires=2)
@qml.qnode(dev, interface='numpy')
def circuit1(theta, matrix):
    qml.RX(theta, wires=0)
    qml.QubitUnitary(matrix, wires=1)
    return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))
  • Actual behavior: When provided with the matrix np.array([[1, 0], [0, 0.70710678 + 0.70710678*1.j]]), an exception is raised: "TypeError: RX: Real scalar parameter expected, got <class 'numpy.complex128'>."

  • Reproduces how often: Seems to happen no matter what gate is put before QubitUnitary, and no matter how many gates there are in between the first and QubitUnitary. When the preceding gate has no parameterized argument, circuit executes without error.

  • System information: Name: PennyLane
    Version: 0.9.0.dev0
    Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
    Home-page: https://github.com/XanaduAI/pennylane
    Author: None
    Author-email: None
    License: Apache License 2.0
    Location: /home/nathan/dev/pennylane
    Requires: numpy, scipy, networkx, autograd, toml, appdirs, semantic-version
    Required-by: PennyLane-qiskit, PennyLane-Qchem, PennyLane-PQ, PennyLane-Forest
    Platform info: Linux-5.3.0-40-generic-x86_64-with-debian-buster-sid
    Python version: 3.7.6
    Numpy version: 1.18.1
    Scipy version: 1.4.1
    Installed devices:

  • qiskit.aer (PennyLane-qiskit-0.8.0)
  • qiskit.basicaer (PennyLane-qiskit-0.8.0)
  • qiskit.ibmq (PennyLane-qiskit-0.8.0)
  • projectq.classical (PennyLane-PQ-0.8.0)
  • projectq.ibm (PennyLane-PQ-0.8.0)
  • projectq.simulator (PennyLane-PQ-0.8.0)
  • forest.numpy_wavefunction (PennyLane-Forest-0.8.0)
  • forest.qpu (PennyLane-Forest-0.8.0)
  • forest.qvm (PennyLane-Forest-0.8.0)
  • forest.wavefunction (PennyLane-Forest-0.8.0)
  • default.gaussian (PennyLane-0.9.0.dev0)
  • default.qubit (PennyLane-0.9.0.dev0)
  • default.tensor (PennyLane-0.9.0.dev0)
  • default.tensor.tf (PennyLane-0.9.0.dev0)

Source code and tracebacks


TypeError Traceback (most recent call last)
in
2 theta = 0.2
3 matrix = np.array([[1, 0], [0, 0.70710678 + 0.70710678*1.j]])
----> 4 print(circuit1(theta, matrix))

~/dev/pennylane/pennylane/interfaces/autograd.py in call(self, *args, **kwargs)
45 # prevents autograd boxed arguments from going through to evaluate
46 args = autograd.builtins.tuple(args) # pylint: disable=no-member
---> 47 return self.evaluate(args, kwargs)
48
49 @staticmethod

~/anaconda3/envs/pennylane0.8/lib/python3.7/site-packages/autograd/tracer.py in f_wrapped(*args, **kwargs)
46 return new_box(ans, trace, node)
47 else:
---> 48 return f_raw(*args, **kwargs)
49 f_wrapped.fun = f_raw
50 f_wrapped._is_autograd_primitive = True

~/dev/pennylane/pennylane/qnodes/base.py in evaluate(self, args, kwargs)
773 if isinstance(self.device, qml.QubitDevice):
774 # TODO: remove this if statement once all devices are ported to the QubitDevice API
--> 775 ret = self.device.execute(self.circuit, return_native_type=temp)
776 else:
777 ret = self.device.execute(

~/dev/pennylane/pennylane/_qubit_device.py in execute(self, circuit, **kwargs)
152
153 # apply all circuit operations
--> 154 self.apply(circuit.operations, rotations=circuit.diagonalizing_gates, **kwargs)
155
156 # generate computational basis samples

~/dev/pennylane/pennylane/plugins/default_qubit.py in apply(self, operations, rotations, **kwargs)
97 # number of wires on device
98 wires = operation.wires
---> 99 par = operation.parameters
100
101 if i > 0 and isinstance(operation, (QubitStateVector, BasisState)):

~/dev/pennylane/pennylane/operation.py in parameters(self)
462 return p
463
--> 464 return [evaluate(p) for p in self.params]
465
466 def queue(self):

~/dev/pennylane/pennylane/operation.py in (.0)
462 return p
463
--> 464 return [evaluate(p) for p in self.params]
465
466 def queue(self):

~/dev/pennylane/pennylane/operation.py in evaluate(p)
459 return p
460 if isinstance(p, VariableRef):
--> 461 p = self.check_domain(p.val)
462 return p
463

~/dev/pennylane/pennylane/operation.py in check_domain(self, p, flattened)
411 if not isinstance(p, numbers.Real):
412 raise TypeError(
--> 413 "{}: Real scalar parameter expected, got {}.".format(self.name, type(p))
414 )
415

TypeError: RX: Real scalar parameter expected, got <class 'numpy.complex128'>.

@co9olguy

This comment has been minimized.

Copy link
Member Author

@co9olguy co9olguy commented Mar 10, 2020

Note that if we use a real matrix, the circuit executes without complaint

@josh146 josh146 added bug core labels Mar 11, 2020
@josh146

This comment has been minimized.

Copy link
Member

@josh146 josh146 commented Mar 11, 2020

I think two things are happening here.

Ideally, we want the following behaviour:

  • Arrays/matrices should always be passed as auxiliary arguments, simply so pennylane knows they are not differentiable. This is independent of them becoming Variables or not.

  • Arrays/matrices passed as positional arguments should still evaluate on the forward pass, but fail on the backward pass.

However, when arrays are converted to variables, there is an issue. In the base QNode:

Variable.positional_arg_values = np.array(list(_flatten(args)))

Due to NumPy casting rules, the presence of a single complex value will cause the entire array to become complex valued, even values that were originally real. This explains why this bug only affects circuits that contain QubitUnitary with complex arrays and parametrized gates.

Question: is this a new issue, or has this always been present?

Possible solutions:

  1. Enforce arrays always being passed as auxiliary arguments.
    Pro: easy. Cons: loss of flexibility

  2. Do not use a numpy array to store variable values, to allow multiple types. Cons: ?

  3. Cast gate parameters to floats, and only raise an error if the imaginary part is non-zero. Pro: easy, makes sense, gates already declare they require real parameters. Cons: ?

I think (3) is the best solution, alongside making it more clear to the user that arrays should always be auxiliary if they plan to do backpropagation/gradient computations.

@johannesjmeyer

This comment has been minimized.

Copy link
Collaborator

@johannesjmeyer johannesjmeyer commented Mar 11, 2020

I actually prefer solution (2), just have

Variable.positional_arg_values = list(_flatten(args)))

In that way, gates will still complain when the parameters are complex.

Note that passing arrays as parameters is totally within scope in pennylane (e.g. weights for templates). In this particular case, the problem is that the parameter matrix is used in a place where it can not be differentiated.

@josh146

This comment has been minimized.

Copy link
Member

@josh146 josh146 commented Mar 25, 2020

I'm curious now how much of an impact simply changing

Variable.positional_arg_values = list(_flatten(args)))

and running the tests will have.

@smite

This comment has been minimized.

Copy link
Collaborator

@smite smite commented Mar 27, 2020

(I'm using the primary/auxiliary parameter nomeclature here.)

The quantum circuits in PL are meant to be differentiable wrt. their primary parameters, which can always be treated as a flattenable nested sequence of real scalar values. So by design no complex values can appear as primary arguments. Furthermore, by design matrix-like parameters which are not differentiable should be auxiliary.

Note that this does not prevent passing real arrays as primary arguments (weights in templates, for example).

I would not allow passing complex values to primary parameters even on the forward pass (evaluation only, no differentiation), since this is unnecessary and just confuses the concepts. If it works, the user will be twice as surprised when the differentation suddenly raises an error.

I think @johannesjmeyer 's idea might be good: use a list instead of an array. I thought np.ndarray access was fundamentally faster than list access, but it seems that Python lists are actually implemented using some kind of dynamic array with O(1) access, so it's certainly worth testing. If there is no performance loss, it seems like a clean solution.

https://wiki.python.org/moin/TimeComplexity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
4 participants
You can’t perform that action at this time.