PennyLaneAI / pennylane

PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network.
https://pennylane.ai
Apache License 2.0
2.27k stars 585 forks source link

Qubitunitary causes bug with complex matrix #541

Closed co9olguy closed 4 years ago

co9olguy commented 4 years ago

Issue description

Issue posted by user max on the forums

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 .
co9olguy commented 4 years ago

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

josh146 commented 4 years ago

I think two things are happening here.

Ideally, we want the following behaviour:

However, when arrays are converted to variables, there is an issue. In the base QNode: https://github.com/XanaduAI/pennylane/blob/f7ad64a39187a577ff33d1cf3abb57e345eb317a/pennylane/qnodes/base.py#L288

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

(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