Repository: PennyLaneAI/pennylane

Issue #2892: [BUG] Hermitian operator fails during `expval` with TensorFlow
Created: 2022-08-04T13:47:18Z
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.

Duplicate issue #2132: [BUG] Update validation check in `qml.Hermitian` to support multiple interfaces
Created: 2022-01-27T17:28:30Z
Expected behavior
The following executes correctly:
import pennylane as qml
import numpy as np
import tensorflow as tf

dev = qml.device('default.qubit', wires=1, shots=10)

arr = np.array(
    [
        1.32,
        2.312,
    ]
)

herm = tf.Variable(np.outer(arr, arr))

@qml.qnode(dev, interface='tf')
def circuit():
    return qml.sample(qml.Hermitian(herm, wires=0))

res = circuit()
Actual behavior
AttributeError: 'tensorflow.python.framework.ops.EagerTensor' object has no attribute 'conj' is raised due to the
        if not qml.math.allclose(A, A.conj().T):
            raise ValueError("Observable must be Hermitian.")
validation check.
Additional information
To resolve the issue, it may be good to consider if we could support conj and/or T in qml.math for multiple interfaces.
On top of that, however, qml.math.allclose will break jittability, hence potentially omitting the validation as a whole should also be considered.
Source code
No response
Tracebacks
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-1-0a0e7a84a095> in <module>
     21 print(res, type(res), res.dtype)
     22 
---> 23 circuit.qtape.get_output_domain()

~/xanadu/pennylane/pennylane/tape/tape.py in get_output_domain(self)
   1079 
   1080                 output_domain = (
-> 1081                     int if all(np.issubdtype(e.dtype, int) for e in observable.eigvals) else float
   1082                 )
   1083 

~/xanadu/pennylane/pennylane/measure.py in eigvals(self)
    144         if self.obs is not None:
    145             try:
--> 146                 return self.obs.eigvals
    147             except NotImplementedError:
    148                 pass

~/xanadu/pennylane/pennylane/ops/qubit/observables.py in eigvals(self)
    102             array: array containing the eigenvalues of the Hermitian observable
    103         """
--> 104         return self.eigendecomposition["eigval"]
    105 
    106     def diagonalizing_gates(self):

~/xanadu/pennylane/pennylane/ops/qubit/observables.py in eigendecomposition(self)
     83             dict[str, array]: dictionary containing the eigenvalues and the eigenvectors of the Hermitian observable
     84         """
---> 85         Hmat = self.matrix
     86         Hmat = qml.math.to_numpy(Hmat)
     87         Hkey = tuple(Hmat.flatten().tolist())

~/xanadu/pennylane/pennylane/operation.py in matrix(self)
    342             array: matrix representation
    343         """
--> 344         return self._matrix(*self.parameters)
    345 
    346     @classmethod

~/xanadu/pennylane/pennylane/ops/qubit/observables.py in _matrix(cls, *params)
     66             raise ValueError("Observable must be a square matrix.")
     67 
---> 68         if not qml.math.allclose(A, A.conj().T):
     69             raise ValueError("Observable must be Hermitian.")
     70 

~/anaconda3/lib/python3.8/site-packages/tensorflow/python/framework/ops.py in __getattr__(self, name)
    440         from tensorflow.python.ops.numpy_ops import np_config
    441         np_config.enable_numpy_behavior()""".format(type(self).__name__, name))
--> 442     self.__getattribute__(name)
    443 
    444   @staticmethod

AttributeError: 'tensorflow.python.framework.ops.EagerTensor' object has no attribute 'conj'
System information
Name: PennyLane
Version: 0.21.0.dev0
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: /home/antal/xanadu/pennylane
Requires: numpy, scipy, networkx, retworkx, autograd, toml, appdirs, semantic_version, autoray, cachetools, pennylane-lightning
Required-by: PennyLane-Cirq, amazon-braket-pennylane-plugin, PennyLane-Orquestra, pennylane-qulacs, PennyLane-Honeywell, PennyLane-qiskit, PennyLane-AQT, PennyLane-PQ, PennyLane-Forest, PennyLane-qsharp, PennyLane-Qchem, PennyLane-IonQ, PennyLane-SF, PennyLane-Lightning
Platform info:           Linux-5.13.0-27-generic-x86_64-with-glibc2.10
Python version:          3.8.5
Numpy version:           1.19.2
Scipy version:           1.7.3
Installed devices:
- cirq.mixedsimulator (PennyLane-Cirq-0.19.0)
- cirq.pasqal (PennyLane-Cirq-0.19.0)
- cirq.qsim (PennyLane-Cirq-0.19.0)
- cirq.qsimh (PennyLane-Cirq-0.19.0)
- cirq.simulator (PennyLane-Cirq-0.19.0)
- braket.aws.qubit (amazon-braket-pennylane-plugin-1.5.3)
- braket.local.qubit (amazon-braket-pennylane-plugin-1.5.3)
- orquestra.forest (PennyLane-Orquestra-0.15.0)
- orquestra.ibmq (PennyLane-Orquestra-0.15.0)
- orquestra.qiskit (PennyLane-Orquestra-0.15.0)
- orquestra.qulacs (PennyLane-Orquestra-0.15.0)
- qulacs.simulator (pennylane-qulacs-0.17.0.dev0)
- honeywell.hqs (PennyLane-Honeywell-0.16.0.dev0)
- qiskit.aer (PennyLane-qiskit-0.18.0.dev0)
- qiskit.basicaer (PennyLane-qiskit-0.18.0.dev0)
- qiskit.ibmq (PennyLane-qiskit-0.18.0.dev0)
- aqt.noisy_sim (PennyLane-AQT-0.18.0)
- aqt.sim (PennyLane-AQT-0.18.0)
- projectq.classical (PennyLane-PQ-0.18.0.dev0)
- projectq.ibm (PennyLane-PQ-0.18.0.dev0)
- projectq.simulator (PennyLane-PQ-0.18.0.dev0)
- forest.numpy_wavefunction (PennyLane-Forest-0.18.0.dev0)
- forest.qvm (PennyLane-Forest-0.18.0.dev0)
- forest.wavefunction (PennyLane-Forest-0.18.0.dev0)
- microsoft.QuantumSimulator (PennyLane-qsharp-0.19.0)
- ionq.qpu (PennyLane-IonQ-0.17.0.dev0)
- ionq.simulator (PennyLane-IonQ-0.17.0.dev0)
- strawberryfields.fock (PennyLane-SF-0.20.0.dev0)
- strawberryfields.gaussian (PennyLane-SF-0.20.0.dev0)
- strawberryfields.gbs (PennyLane-SF-0.20.0.dev0)
- strawberryfields.remote (PennyLane-SF-0.20.0.dev0)
- strawberryfields.tf (PennyLane-SF-0.20.0.dev0)
- lightning.qubit (PennyLane-Lightning-0.21.0.dev0)
- default.gaussian (PennyLane-0.21.0.dev0)
- default.mixed (PennyLane-0.21.0.dev0)
- default.qubit (PennyLane-0.21.0.dev0)
- default.qubit.autograd (PennyLane-0.21.0.dev0)
- default.qubit.jax (PennyLane-0.21.0.dev0)
- default.qubit.tf (PennyLane-0.21.0.dev0)
- default.qubit.torch (PennyLane-0.21.0.dev0)
Existing GitHub issues

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

Closing comment (2023-02-13T18:00:53Z): duplicate of #2892, resolved