def test_tf(self, tol): """Tests that the output of the parameter-shift CV transform can be executed using TF""" tf = pytest.importorskip("tensorflow") from pennylane.interfaces.tf import TFInterface dev = qml.device("default.gaussian", wires=1) params = tf.Variable([0.543, -0.654], dtype=tf.float64) with tf.GradientTape() as t: with TFInterface.apply(qml.tape.CVParamShiftTape()) as tape: qml.Squeezing(params[0], 0, wires=0) qml.Rotation(params[1], wires=0) qml.var(qml.X(wires=[0])) tapes, fn = qml.gradients.param_shift_cv(tape, dev) jac = fn([tp.execute(dev) for tp in tapes]) res = jac[0, 1] r, phi = 1.0 * params expected = np.array([ 2 * np.exp(2 * r) * np.sin(phi)**2 - 2 * np.exp(-2 * r) * np.cos(phi)**2, 2 * np.sinh(2 * r) * np.sin(2 * phi), ]) assert np.allclose(jac, expected, atol=tol, rtol=0) grad = t.jacobian(res, params) expected = np.array([ 4 * np.cosh(2 * r) * np.sin(2 * phi), 4 * np.cos(2 * phi) * np.sinh(2 * r) ]) assert np.allclose(grad, expected, atol=tol, rtol=0)
def test_jacobian(self, mocker, tol): """Test jacobian calculation""" spy = mocker.spy(JacobianTape, "jacobian") a = tf.Variable(0.1, dtype=tf.float64) b = tf.Variable(0.2, dtype=tf.float64) dev = qml.device("default.qubit", wires=2) with tf.GradientTape() as tape: with TFInterface.apply(JacobianTape()) as qtape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) qml.expval(qml.PauliY(1)) assert qtape.trainable_params == [0, 1] res = qtape.execute(dev) assert isinstance(res, tf.Tensor) assert res.shape == (2, ) expected = [tf.cos(a), -tf.cos(a) * tf.sin(b)] assert np.allclose(res, expected, atol=tol, rtol=0) res = tape.jacobian(res, [a, b]) expected = [[-tf.sin(a), tf.sin(a) * tf.sin(b)], [0, -tf.cos(a) * tf.cos(b)]] assert np.allclose(res, expected, atol=tol, rtol=0) spy.assert_called()
def test_ragged_differentiation(self, tol): """Tests correct output shape and evaluation for a tape with prob and expval outputs""" dev = qml.device("default.qubit", wires=2) x = tf.Variable(0.543, dtype=tf.float64) y = tf.Variable(-0.654, dtype=tf.float64) with tf.GradientTape() as tape: with TFInterface.apply(JacobianTape()) as qtape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) qml.probs(wires=[1]) res = qtape.execute(dev) expected = np.array([ tf.cos(x), (1 + tf.cos(x) * tf.cos(y)) / 2, (1 - tf.cos(x) * tf.cos(y)) / 2 ]) assert np.allclose(res, expected, atol=tol, rtol=0) res = tape.jacobian(res, [x, y]) expected = np.array([ [ -tf.sin(x), -tf.sin(x) * tf.cos(y) / 2, tf.cos(y) * tf.sin(x) / 2 ], [0, -tf.cos(x) * tf.sin(y) / 2, tf.cos(x) * tf.sin(y) / 2], ]) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_jacobian_dtype(self, tol): """Test calculating the jacobian with a different datatype. Here, we specify tf.float32, as opposed to the default value of tf.float64.""" a = tf.Variable(0.1, dtype=tf.float32) b = tf.Variable(0.2, dtype=tf.float32) dev = qml.device("default.qubit", wires=2) with tf.GradientTape() as tape: with TFInterface.apply(JacobianTape(), dtype=tf.float32) as qtape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) qml.expval(qml.PauliY(1)) assert qtape.trainable_params == [0, 1] res = qtape.execute(dev) assert isinstance(res, tf.Tensor) assert res.shape == (2, ) assert res.dtype is tf.float32 res = tape.jacobian(res, [a, b]) assert [r.dtype is tf.float32 for r in res]
def test_repeated_interface_construction(self): """Test that the interface is correctly applied multiple times""" with TFInterface.apply(JacobianTape()) as tape: qml.RX(0.5, wires=0) qml.expval(qml.PauliX(0)) assert tape.interface == "tf" assert isinstance(tape, TFInterface) assert tape.__bare__ == JacobianTape assert tape.dtype is tf.float64 TFInterface.apply(tape, dtype=tf.float32) assert tape.interface == "tf" assert isinstance(tape, TFInterface) assert tape.__bare__ == JacobianTape assert tape.dtype is tf.float32
def test_hamiltonian_dif_tensorflow(self): """Tests that the hamiltonian_expand tape transform is differentiable with the Tensorflow interface""" tf = pytest.importorskip("tensorflow") from pennylane.interfaces.tf import TFInterface H = qml.Hamiltonian( [-0.2, 0.5, 1], [qml.PauliX(1), qml.PauliZ(1) @ qml.PauliY(2), qml.PauliZ(0)]) var = tf.Variable([[0.1, 0.67, 0.3], [0.4, -0.5, 0.7]], dtype=tf.float64) output = 0.42294409781940356 output2 = [ 9.68883500e-02, -2.90832724e-01, -1.04448033e-01, -1.94289029e-09, 3.50307411e-01, -3.41123470e-01, ] with tf.GradientTape() as gtape: with qml.tape.JacobianTape() as tape: for i in range(2): qml.RX(var[i, 0], wires=0) qml.RX(var[i, 1], wires=1) qml.RX(var[i, 2], wires=2) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[1, 2]) qml.CNOT(wires=[2, 0]) qml.expval(H) TFInterface.apply(tape) tapes, fn = qml.transforms.hamiltonian_expand(tape) res = fn([t.execute(dev) for t in tapes]) assert np.isclose(res, output) g = gtape.gradient(res, var) assert np.allclose(list(g[0]) + list(g[1]), output2)
def test_differentiable_expand(self, tol): """Test that operation and nested tapes expansion is differentiable""" class U3(qml.U3): def expand(self): tape = JacobianTape() theta, phi, lam = self.data wires = self.wires tape._ops += [ qml.Rot(lam, theta, -lam, wires=wires), qml.PhaseShift(phi + lam, wires=wires), ] return tape qtape = JacobianTape() dev = qml.device("default.qubit", wires=1) a = np.array(0.1) p = tf.Variable([0.1, 0.2, 0.3], dtype=tf.float64) with tf.GradientTape() as tape: with qtape: qml.RX(a, wires=0) U3(p[0], p[1], p[2], wires=0) qml.expval(qml.PauliX(0)) qtape = TFInterface.apply(qtape.expand()) assert qtape.trainable_params == [1, 2, 3, 4] assert [i.name for i in qtape.operations] == ["RX", "Rot", "PhaseShift"] assert np.all( qtape.get_parameters() == [p[2], p[0], -p[2], p[1] + p[2]]) res = qtape.execute(device=dev) expected = tf.cos(a) * tf.cos(p[1]) * tf.sin( p[0]) + tf.sin(a) * (tf.cos(p[2]) * tf.sin(p[1]) + tf.cos(p[0]) * tf.cos(p[1]) * tf.sin(p[2])) assert np.allclose(res, expected, atol=tol, rtol=0) res = tape.jacobian(res, p) expected = np.array([ tf.cos(p[1]) * (tf.cos(a) * tf.cos(p[0]) - tf.sin(a) * tf.sin(p[0]) * tf.sin(p[2])), tf.cos(p[1]) * tf.cos(p[2]) * tf.sin(a) - tf.sin(p[1]) * (tf.cos(a) * tf.sin(p[0]) + tf.cos(p[0]) * tf.sin(a) * tf.sin(p[2])), tf.sin(a) * (tf.cos(p[0]) * tf.cos(p[1]) * tf.cos(p[2]) - tf.sin(p[1]) * tf.sin(p[2])), ]) assert np.allclose(res, expected, atol=tol, rtol=0)
def to_tf(self, dtype=None): """Apply the TensorFlow interface to the internal quantum tape. Args: dtype (tf.dtype): The dtype that the TensorFlow QNode should output. If not provided, the default is ``tf.float64``. Raises: .QuantumFunctionError: if TensorFlow >= 2.1 is not installed """ # pylint: disable=import-outside-toplevel try: import tensorflow as tf from pennylane.interfaces.tf import TFInterface if self.interface != "tf" and self.interface is not None: # Since the interface is changing, need to re-validate the tape class. self._tape, interface, self.device, diff_options = self.get_tape( self._original_device, "tf", self.diff_method ) self.interface = interface self.diff_options.update(diff_options) else: self.interface = "tf" if not isinstance(self.dtype, tf.DType): self.dtype = None self.dtype = dtype or self.dtype or TFInterface.dtype if self.qtape is not None: TFInterface.apply(self.qtape, dtype=tf.as_dtype(self.dtype)) except ImportError as e: raise qml.QuantumFunctionError( "TensorFlow not found. Please install the latest " "version of TensorFlow to enable the 'tf' interface." ) from e
def test_sampling(self): """Test sampling works as expected""" dev = qml.device("default.qubit", wires=2, shots=10) with tf.GradientTape() as tape: with TFInterface.apply(JacobianTape()) as qtape: qml.Hadamard(wires=[0]) qml.CNOT(wires=[0, 1]) qml.sample(qml.PauliZ(0)) qml.sample(qml.PauliX(1)) res = qtape.execute(dev) assert res.shape == (2, 10) assert isinstance(res, tf.Tensor)
def test_execution(self): """Test execution""" a = tf.Variable(0.1) dev = qml.device("default.qubit", wires=1) with tf.GradientTape() as tape: with TFInterface.apply(JacobianTape()) as qtape: qml.RY(a, wires=0) qml.RX(0.2, wires=0) qml.expval(qml.PauliZ(0)) assert qtape.trainable_params == [0] res = qtape.execute(dev) assert isinstance(res, tf.Tensor) assert res.shape == (1, )
def test_get_parameters(self): """Test that the get parameters function correctly sets and returns the trainable parameters""" a = tf.Variable(0.1) b = tf.constant(0.2) c = tf.Variable(0.3) d = 0.4 with tf.GradientTape() as tape: with TFInterface.apply(JacobianTape()) as qtape: qml.Rot(a, b, c, wires=0) qml.RX(d, wires=1) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliX(0)) assert qtape.trainable_params == [0, 2] assert np.all(qtape.get_parameters() == [a, c])
def test_no_trainable_parameters(self, tol): """Test evaluation if there are no trainable parameters""" dev = qml.device("default.qubit", wires=2) with tf.GradientTape() as tape: with TFInterface.apply(JacobianTape()) as qtape: qml.RY(0.2, wires=0) qml.RX(tf.constant(0.1), wires=0) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) qml.expval(qml.PauliZ(1)) assert qtape.trainable_params == [] res = qtape.execute(dev) assert res.shape == (2, ) assert isinstance(res, tf.Tensor)
def test_matrix_parameter(self, U, tol): """Test that the TF interface works correctly with a matrix parameter""" a = tf.Variable(0.1, dtype=tf.float64) dev = qml.device("default.qubit", wires=2) with tf.GradientTape() as tape: with TFInterface.apply(JacobianTape()) as qtape: qml.QubitUnitary(U, wires=0) qml.RY(a, wires=0) qml.expval(qml.PauliZ(0)) assert qtape.trainable_params == [1] res = qtape.execute(dev) assert np.allclose(res, -tf.cos(a), atol=tol, rtol=0) res = tape.jacobian(res, a) assert np.allclose(res, tf.sin(a), atol=tol, rtol=0)
def test_jacobian_options(self, mocker, tol): """Test setting jacobian options""" spy = mocker.spy(JacobianTape, "numeric_pd") a = tf.Variable([0.1, 0.2]) dev = qml.device("default.qubit", wires=1) with tf.GradientTape() as tape: with TFInterface.apply(JacobianTape()) as qtape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) res = qtape.execute(dev) qtape.jacobian_options = {"h": 1e-8, "order": 2} tape.jacobian(res, a) for args in spy.call_args_list: assert args[1]["order"] == 2 assert args[1]["h"] == 1e-8
def test_reusing_pre_constructed_quantum_tape(self, tol): """Test re-using a quantum tape that was previously constructed *outside of* a gradient tape, by passing new parameters""" a = tf.Variable(0.1, dtype=tf.float64) b = tf.Variable(0.2, dtype=tf.float64) dev = qml.device("default.qubit", wires=2) with TFInterface.apply(JacobianTape()) as qtape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) qml.expval(qml.PauliY(1)) with tf.GradientTape() as tape: qtape.set_parameters([a, b], trainable_only=False) qtape._update_trainable_params() assert qtape.trainable_params == [0, 1] res = qtape.execute(dev) jac = tape.jacobian(res, [a, b]) a = tf.Variable(0.54, dtype=tf.float64) b = tf.Variable(0.8, dtype=tf.float64) with tf.GradientTape() as tape: res2 = qtape.execute(dev, params=[2 * a, b]) expected = [tf.cos(2 * a), -tf.cos(2 * a) * tf.sin(b)] assert np.allclose(res2, expected, atol=tol, rtol=0) jac2 = tape.jacobian(res2, [a, b]) expected = [ [-2 * tf.sin(2 * a), 2 * tf.sin(2 * a) * tf.sin(b)], [0, -tf.cos(2 * a) * tf.cos(b)], ] assert np.allclose(jac2, expected, atol=tol, rtol=0)
def test_classical_processing(self, tol): """Test classical processing within the quantum tape""" a = tf.Variable(0.1, dtype=tf.float64) b = tf.constant(0.2, dtype=tf.float64) c = tf.Variable(0.3, dtype=tf.float64) dev = qml.device("default.qubit", wires=1) with tf.GradientTape() as tape: with TFInterface.apply(JacobianTape()) as qtape: qml.RY(a * c, wires=0) qml.RZ(b, wires=0) qml.RX(c + c**2 + tf.sin(a), wires=0) qml.expval(qml.PauliZ(0)) assert qtape.trainable_params == [0, 2] assert qtape.get_parameters() == [a * c, c + c**2 + tf.sin(a)] res = qtape.execute(dev) res = tape.jacobian(res, [a, b, c]) assert isinstance(res[0], tf.Tensor) assert res[1] is None assert isinstance(res[2], tf.Tensor)