pennylane icon indicating copy to clipboard operation
pennylane copied to clipboard

[BUG] Hermitian operator fails during `expval` with TensorFlow

Open jackaraz opened this issue 2 years ago • 1 comments

Expected behavior

Hi, I'm trying to compute the expectation value of a Hermitian operator which works with the NumPy backend:

dev = qml.device("default.qubit", wires=2)

@qml.qnode(dev)
def circuit(K):
    return qml.expval(qml.Hermitian(K, wires=range(2)))
state = tf.constant([[1,2,3,4]], dtype=tf.float64)
K = tf.transpose(state)@state / 16.
circuit(K.numpy())
# tensor(0.0625, requires_grad=True)

However, when initiated with TensorFlow backend, the same code raises an AttributeError (see the details below). I would like to be able to take the gradient of this operator eventually. However, I'm afraid it won't be possible with TensorFlow if I convert it to NumPy to compute the expectation value. As far as I know, it won't compute the gradient if I take the state and compute the expectation value outside the circuit since there will be complex numbers. Would it work correctly if I only used the real portion of the expectation value after computing it out of the circuit? Any workaround would be appreciated if there is no quick fix.

Thanks!

Actual behavior

Code crashes and raises an AttributeError.

Additional information

No response

Source code

import pennylane as qml
import tensorflow as tf

dev = qml.device("default.qubit.tf", wires=2)

@qml.qnode(dev, interface="tf", diff_method="backprop")
def circuit(K):
    return qml.expval(qml.Hermitian(K, wires=range(2)))
state = tf.constant([[1,2,3,4]], dtype=tf.float64)
K = tf.transpose(state)@state / 16.
circuit(K)

### Second option

dev2 = qml.device("default.qubit.tf", wires=2)

@qml.qnode(dev2, interface="tf", diff_method="backprop")
def circuit2(op):
    return qml.expval(op)
circuit2(qml.Hermitian(K, wires=range(2)))

Tracebacks

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [46], in <cell line: 3>()
      1 state = tf.constant([[1,2,3,4]], dtype=tf.float64)
      2 K = tf.transpose(state)@state / 16.
----> 3 circuit(K)

File ~/packages/miniforge3/lib/python3.9/site-packages/pennylane/qnode.py:619, in QNode.__call__(self, *args, **kwargs)
    612 using_custom_cache = (
    613     hasattr(cache, "__getitem__")
    614     and hasattr(cache, "__setitem__")
    615     and hasattr(cache, "__delitem__")
    616 )
    617 self._tape_cached = using_custom_cache and self.tape.hash in cache
--> 619 res = qml.execute(
    620     [self.tape],
    621     device=self.device,
    622     gradient_fn=self.gradient_fn,
    623     interface=self.interface,
    624     gradient_kwargs=self.gradient_kwargs,
    625     override_shots=override_shots,
    626     **self.execute_kwargs,
    627 )
    629 if autograd.isinstance(res, (tuple, list)) and len(res) == 1:
    630     # If a device batch transform was applied, we need to 'unpack'
    631     # the returned tuple/list to a float.
   (...)
    638     # TODO: find a more explicit way of determining that a batch transform
    639     # was applied.
    641     res = res[0]

File ~/packages/miniforge3/lib/python3.9/site-packages/pennylane/interfaces/execution.py:344, in execute(tapes, device, gradient_fn, interface, mode, gradient_kwargs, cache, cachesize, max_diff, override_shots, expand_fn, max_expansion, device_batch_transform)
    340     return batch_fn(res)
    342 if gradient_fn == "backprop" or interface is None:
    343     return batch_fn(
--> 344         qml.interfaces.cache_execute(
    345             batch_execute, cache, return_tuple=False, expand_fn=expand_fn
    346         )(tapes)
    347     )
    349 # the default execution function is batch_execute
    350 execute_fn = qml.interfaces.cache_execute(batch_execute, cache, expand_fn=expand_fn)

File ~/packages/miniforge3/lib/python3.9/site-packages/pennylane/interfaces/execution.py:172, in cache_execute.<locals>.wrapper(tapes, **kwargs)
    168         return (res, []) if return_tuple else res
    170 else:
    171     # execute all unique tapes that do not exist in the cache
--> 172     res = fn(execution_tapes.values(), **kwargs)
    174 final_res = []
    176 for i, tape in enumerate(tapes):

File ~/packages/miniforge3/lib/python3.9/site-packages/pennylane/interfaces/execution.py:97, in cache_execute.<locals>.fn(tapes, **kwargs)
     95 def fn(tapes, **kwargs):  # pylint: disable=function-redefined
     96     tapes = [expand_fn(tape) for tape in tapes]
---> 97     return original_fn(tapes, **kwargs)

File ~/packages/miniforge3/lib/python3.9/contextlib.py:79, in ContextDecorator.__call__.<locals>.inner(*args, **kwds)
     76 @wraps(func)
     77 def inner(*args, **kwds):
     78     with self._recreate_cm():
---> 79         return func(*args, **kwds)

File ~/packages/miniforge3/lib/python3.9/site-packages/pennylane/_qubit_device.py:355, in QubitDevice.batch_execute(self, circuits)
    350 for circuit in circuits:
    351     # we need to reset the device here, else it will
    352     # not start the next computation in the zero state
    353     self.reset()
--> 355     res = self.execute(circuit)
    356     results.append(res)
    358 if self.tracker.active:

File ~/packages/miniforge3/lib/python3.9/site-packages/pennylane/_qubit_device.py:257, in QubitDevice.execute(self, circuit, **kwargs)
    254 self.check_validity(circuit.operations, circuit.observables)
    256 # apply all circuit operations
--> 257 self.apply(circuit.operations, rotations=circuit.diagonalizing_gates, **kwargs)
    259 # generate computational basis samples
    260 if self.shots is not None or circuit.is_sampled:

File ~/packages/miniforge3/lib/python3.9/site-packages/pennylane/tape/tape.py:1319, in QuantumTape.diagonalizing_gates(self)
   1315 for observable in self.observables:
   1316     # some observables do not have diagonalizing gates,
   1317     # in which case we just don't append any
   1318     try:
-> 1319         rotation_gates.extend(observable.diagonalizing_gates())
   1320     except qml.operation.DiagGatesUndefinedError:
   1321         pass

File ~/packages/miniforge3/lib/python3.9/site-packages/pennylane/ops/qubit/observables.py:171, in Hermitian.diagonalizing_gates(self)
    164 """Return the gate set that diagonalizes a circuit according to the
    165 specified Hermitian observable.
    166 
    167 Returns:
    168     list: list containing the gates diagonalizing the Hermitian observable
    169 """
    170 # note: compute_diagonalizing_gates has a custom signature, which is why we overwrite this method
--> 171 return self.compute_diagonalizing_gates(self.eigendecomposition["eigvec"], self.wires)

File ~/packages/miniforge3/lib/python3.9/site-packages/pennylane/ops/qubit/observables.py:113, in Hermitian.eigendecomposition(self)
    101 @property
    102 def eigendecomposition(self):
    103     """Return the eigendecomposition of the matrix specified by the Hermitian observable.
    104 
    105     This method uses pre-stored eigenvalues for standard observables where
   (...)
    111         dict[str, array]: dictionary containing the eigenvalues and the eigenvectors of the Hermitian observable
    112     """
--> 113     Hmat = self.matrix()
    114     Hmat = qml.math.to_numpy(Hmat)
    115     Hkey = tuple(Hmat.flatten().tolist())

File ~/packages/miniforge3/lib/python3.9/site-packages/pennylane/operation.py:585, in Operator.matrix(self, wire_order)
    565 def matrix(self, wire_order=None):
    566     r"""Representation of the operator as a matrix in the computational basis.
    567 
    568     If ``wire_order`` is provided, the numerical representation considers the position of the
   (...)
    583         tensor_like: matrix representation
    584     """
--> 585     canonical_matrix = self.compute_matrix(*self.parameters, **self.hyperparameters)
    587     if wire_order is None or self.wires == Wires(wire_order):
    588         return canonical_matrix

File ~/packages/miniforge3/lib/python3.9/site-packages/pennylane/ops/qubit/observables.py:96, in Hermitian.compute_matrix(A)
     93 if A.shape[0] != A.shape[1]:
     94     raise ValueError("Observable must be a square matrix.")
---> 96 if not qml.math.allclose(A, A.conj().T):
     97     raise ValueError("Observable must be Hermitian.")
     99 return A

File ~/packages/miniforge3/lib/python3.9/site-packages/tensorflow/python/framework/ops.py:513, in Tensor.__getattr__(self, name)
    505 if name in {"T", "astype", "ravel", "transpose", "reshape", "clip", "size",
    506             "tolist", "data"}:
    507   # TODO(wangpeng): Export the enable_numpy_behavior knob
    508   raise AttributeError("""
    509     '{}' object has no attribute '{}'.
    510     If you are looking for numpy-related methods, please run the following:
    511     from tensorflow.python.ops.numpy_ops import np_config
    512     np_config.enable_numpy_behavior()""".format(type(self).__name__, name))
--> 513 self.__getattribute__(name)

AttributeError: 'tensorflow.python.framework.ops.EagerTensor' object has no attribute 'conj'

System information

Name: PennyLane
Version: 0.24.0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/XanaduAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /Users/jackaraz/packages/miniforge3/lib/python3.9/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, retworkx, scipy, semantic-version, toml
Required-by: PennyLane-Lightning, qhbm

Platform info:           macOS-12.5-arm64-arm-64bit
Python version:          3.9.10
Numpy version:           1.21.6
Scipy version:           1.8.0
Installed devices:
- lightning.qubit (PennyLane-Lightning-0.24.0)
- default.gaussian (PennyLane-0.24.0)
- default.mixed (PennyLane-0.24.0)
- default.qubit (PennyLane-0.24.0)
- default.qubit.autograd (PennyLane-0.24.0)
- default.qubit.jax (PennyLane-0.24.0)
- default.qubit.tf (PennyLane-0.24.0)
- default.qubit.torch (PennyLane-0.24.0)


### Existing GitHub issues

- [X] I have searched existing GitHub issues to make sure the issue does not already exist.

jackaraz avatar Aug 04 '22 13:08 jackaraz

Hi @jackaraz, thanks for reporting this! We'll be looking into this. :)

antalszava avatar Aug 05 '22 15:08 antalszava

I believe this is solved. Closing issue.

AlbertMitjans avatar Aug 16 '22 08:08 AlbertMitjans