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: qml.QuantumFunctionError: if TensorFlow >= 2.1 is not installed """ # pylint: disable=import-outside-toplevel try: import tensorflow as tf from pennylane.beta.interfaces.tf import TFInterface 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: raise qml.QuantumFunctionError( "TensorFlow not found. Please install the latest " "version of TensorFlow to enable the 'tf' interface.")
def test_repeated_interface_construction(self): """Test that the interface is correctly applied multiple times""" with TFInterface.apply(QuantumTape()) as tape: qml.RX(0.5, wires=0) expval(qml.PauliX(0)) assert tape.interface == "tf" assert isinstance(tape, TFInterface) assert tape.__bare__ == QuantumTape assert tape.dtype is tf.float64 TFInterface.apply(tape, dtype=tf.float32) assert tape.interface == "tf" assert isinstance(tape, TFInterface) assert tape.__bare__ == QuantumTape assert tape.dtype is tf.float32
def test_jacobian(self, mocker, tol): """Test jacobian calculation""" spy = mocker.spy(QuantumTape, "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(QuantumTape()) as qtape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(0)) 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(QuantumTape()) as qtape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(0)) 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(QuantumTape(), dtype=tf.float32) as qtape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(0)) 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_differentiable_expand(self, mocker, tol): """Test that operation and nested tapes expansion is differentiable""" mock = mocker.patch.object(qml.operation.Operation, "do_check_domain", False) class U3(qml.U3): def expand(self): tape = QuantumTape() 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 = QuantumTape() 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) 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 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(QuantumTape()) as qtape: qml.Hadamard(wires=[0]) qml.CNOT(wires=[0, 1]) sample(qml.PauliZ(0)) 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(QuantumTape()) as qtape: qml.RY(a, wires=0) qml.RX(0.2, wires=0) 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(QuantumTape()) as qtape: qml.Rot(a, b, c, wires=0) qml.RX(d, wires=1) qml.CNOT(wires=[0, 1]) 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(QuantumTape()) as qtape: qml.RY(0.2, wires=0) qml.RX(tf.constant(0.1), wires=0) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(0)) expval(qml.PauliZ(1)) assert qtape.trainable_params == set() 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(QuantumTape()) as qtape: qml.QubitUnitary(U, wires=0) qml.RY(a, wires=0) 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(QuantumTape, "numeric_pd") a = tf.Variable([0.1, 0.2]) dev = qml.device("default.qubit", wires=1) with tf.GradientTape() as tape: with TFInterface.apply(QuantumTape()) as qtape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) 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(QuantumTape()) as qtape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(0)) 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(QuantumTape()) as qtape: qml.RY(a * c, wires=0) qml.RZ(b, wires=0) qml.RX(c + c**2 + tf.sin(a), wires=0) 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)