def test_set_and_get(self): """Test that the caching attribute can be set and accessed""" tape = QuantumTape() assert tape.caching == 0 tape = QuantumTape(caching=10) assert tape.caching == 10
def test_differentiable_expand(self, mocker, tol): """Test that operation and nested tapes expansion is differentiable""" spy = mocker.spy(QuantumTape, "jacobian") 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.tf", 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 = qtape.expand() assert [i.name for i in qtape.operations] == ["RX", "Rot", "PhaseShift"] assert np.all( qtape.get_parameters() == [a, 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) spy.assert_not_called()
def test_sampling(self): """Test that the tape correctly marks itself as returning samples""" with QuantumTape() as tape: expval(qml.PauliZ(wires=1)) assert not tape.is_sampled with QuantumTape() as tape: sample(qml.PauliZ(wires=0)) assert tape.is_sampled
def test_nested_tape(self): """Test that a nested tape properly expands""" with QuantumTape() as tape1: with QuantumTape() as tape2: op1 = qml.RX(0.543, wires=0) op2 = qml.RY(0.1, wires=0) assert tape1.num_params == 2 assert tape1.operations == [tape2] new_tape = tape1.expand() assert new_tape.num_params == 2 assert len(new_tape.operations) == 2 assert isinstance(new_tape.operations[0], qml.RX) assert isinstance(new_tape.operations[1], qml.RY)
def test_nesting_and_decomposition(self): """Test an example that contains nested tapes and operation decompositions.""" with QuantumTape() as tape: qml.BasisState(np.array([1, 1]), wires=[0, "a"]) with QuantumTape() as tape2: qml.Rot(0.543, 0.1, 0.4, wires=0) qml.CNOT(wires=[0, "a"]) qml.RY(0.2, wires="a") probs(wires=0), probs(wires="a") new_tape = tape.expand() assert len(new_tape.operations) == 5
def cost(a, b, device): with AutogradInterface.apply(QuantumTape()) as tape: qml.RY(a, wires=0) qml.RX(b, wires=0) expval(qml.PauliZ(0)) assert tape.trainable_params == {0} return tape.execute(device)
def cost(a, b, c, device): with QuantumTape() as tape: qml.RY(a * c, wires=0) qml.RZ(b, wires=0) qml.RX(c + c**2 + np.sin(a), wires=0) expval(qml.PauliZ(0)) return tape.execute(device)
def cost(a, b, device): with QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=0) expval(qml.PauliZ(0)) expval(qml.PauliY(0)) return tape.execute(device)
def test_jacobian(self, mocker, tol): """Test jacobian calculation""" spy = mocker.spy(QuantumTape, "jacobian") a = np.array(0.1, requires_grad=True) b = np.array(0.2, requires_grad=True) def cost(a, b, device): with QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=0) expval(qml.PauliZ(0)) expval(qml.PauliY(0)) return tape.execute(device) dev = qml.device("default.qubit.autograd", wires=2) res = qml.jacobian(cost)(a, b, device=dev) spy.assert_not_called() assert res.shape == (2, 2) # compare to standard tape jacobian with QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=0) expval(qml.PauliZ(0)) expval(qml.PauliY(0)) expected = tape.jacobian(dev) assert expected.shape == (2, 2) assert np.allclose(res, expected, atol=tol, rtol=0)
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_classical_processing(self, mocker, tol): """Test classical processing within the quantum tape""" spy = mocker.spy(QuantumTape, "jacobian") 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.tf", wires=1) with tf.GradientTape() as tape: with 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.get_parameters() == [a * c, b, 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) spy.assert_not_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_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_val = 0.543 y_val = -0.654 x = torch.tensor(x_val, requires_grad=True) y = torch.tensor(y_val, requires_grad=True) with TorchInterface.apply(QuantumTape()) as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(0)) probs(wires=[1]) res = tape.execute(dev) expected = np.array([ np.cos(x_val), (1 + np.cos(x_val) * np.cos(y_val)) / 2, (1 - np.cos(x_val) * np.cos(y_val)) / 2, ]) assert np.allclose(res.detach().numpy(), expected, atol=tol, rtol=0) loss = torch.sum(res) loss.backward() expected = np.array([ -np.sin(x_val) + -np.sin(x_val) * np.cos(y_val) / 2 + np.cos(y_val) * np.sin(x_val) / 2, -np.cos(x_val) * np.sin(y_val) / 2 + np.cos(x_val) * np.sin(y_val) / 2, ]) assert np.allclose(x.grad, expected[0], atol=tol, rtol=0) assert np.allclose(y.grad, expected[1], atol=tol, rtol=0)
def test_classical_processing(self, tol): """Test classical processing within the quantum tape""" p_val = [0.1, 0.2] params = torch.tensor(p_val, requires_grad=True) dev = qml.device("default.qubit", wires=1) with TorchInterface.apply(QuantumTape()) as tape: qml.RY(params[0] * params[1], wires=0) qml.RZ(0.2, wires=0) qml.RX(params[1] + params[1]**2 + torch.sin(params[0]), wires=0) expval(qml.PauliZ(0)) assert tape.trainable_params == {0, 2} tape_params = [i.detach().numpy() for i in tape.get_parameters()] assert np.allclose( tape_params, [p_val[0] * p_val[1], p_val[1] + p_val[1]**2 + np.sin(p_val[0])], atol=tol, rtol=0, ) res = tape.execute(dev) res.backward() assert isinstance(params.grad, torch.Tensor) assert params.shape == (2, )
def cost_fn(a, p, device): tape = QuantumTape() with tape: qml.RX(a, wires=0) U3(*p, wires=0) expval(qml.PauliX(0)) tape = tape.expand() assert [i.name for i in tape.operations] == ["RX", "Rot", "PhaseShift"] assert np.all( tape.get_parameters() == [a, p[2], p[0], -p[2], p[1] + p[2]]) return tape.execute(device=device)
def test_jacobian_dtype(self, tol): """Test calculating the jacobian with a different datatype""" a_val = 0.1 b_val = 0.2 a = torch.tensor(a_val, requires_grad=True, dtype=torch.float32) b = torch.tensor(b_val, requires_grad=True, dtype=torch.float32) dev = qml.device("default.qubit", wires=2) with TorchInterface.apply(QuantumTape(), dtype=torch.float32) as tape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(0)) expval(qml.PauliY(1)) assert tape.trainable_params == {0, 1} res = tape.execute(dev) assert isinstance(res, torch.Tensor) assert res.shape == (2, ) assert res.dtype is torch.float32 loss = torch.sum(res) loss.backward() assert a.grad.dtype is torch.float32 assert b.grad.dtype is torch.float32
def test_measurement_expansion(self): """Test that measurement expansion works as expected""" with QuantumTape() as tape: # expands into 2 PauliX qml.BasisState(np.array([1, 1]), wires=[0, "a"]) qml.CNOT(wires=[0, "a"]) qml.RY(0.2, wires="a") probs(wires=0) # expands into RY on wire b expval(qml.PauliZ("a") @ qml.Hadamard("b")) # expands into QubitUnitary on wire 0 var(qml.Hermitian(np.array([[1, 2], [2, 4]]), wires=[0])) new_tape = tape.expand(expand_measurements=True) assert len(new_tape.operations) == 6 expected = [ qml.operation.Probability, qml.operation.Expectation, qml.operation.Variance ] assert [ m.return_type is r for m, r in zip(new_tape.measurements, expected) ] expected = [None, None, None] assert [m.obs is r for m, r in zip(new_tape.measurements, expected)] expected = [None, [1, -1, -1, 1], [0, 5]] assert [ m.eigvals is r for m, r in zip(new_tape.measurements, expected) ]
def test_parameter_transforms(self): """Test that inversion correctly changes trainable parameters""" init_state = np.array([1, 1]) p = [0.1, 0.2, 0.3, 0.4] with QuantumTape() as tape: prep = qml.BasisState(init_state, wires=[0, "a"]) ops = [ qml.RX(p[0], wires=0), qml.Rot(*p[1:], wires=0).inv(), qml.CNOT(wires=[0, "a"]) ] m1 = probs(wires=0) m2 = probs(wires="a") tape.trainable_params = {1, 2} tape.inv() # check that operation order is reversed assert tape.trainable_params == {1, 4} assert tape.get_parameters() == [p[1], p[0]] # undo the inverse tape.inv() assert tape.trainable_params == {1, 2} assert tape.get_parameters() == [p[0], p[1]] assert tape._ops == ops
def test_graph_creation(self, mocker): """Test that the circuit graph is correctly created""" spy = mocker.spy(NewCircuitGraph, "__init__") with QuantumTape() as tape: op = qml.RX(1.0, wires=0) obs = qml.PauliZ(1) expval(obs) # graph has not yet been created assert tape._graph is None spy.assert_not_called() # requesting the graph creates it g = tape.graph assert g.operations == [op] assert g.observables == [obs] assert tape._graph is not None spy.assert_called_once() # calling the graph property again does # not reconstruct the graph g2 = tape.graph assert g2 is g spy.assert_called_once()
def test_inverse(self): """Test that inversion works as expected""" init_state = np.array([1, 1]) p = [0.1, 0.2, 0.3, 0.4] with QuantumTape() as tape: prep = qml.BasisState(init_state, wires=[0, "a"]) ops = [ qml.RX(p[0], wires=0), qml.Rot(*p[1:], wires=0).inv(), qml.CNOT(wires=[0, "a"]) ] m1 = probs(wires=0) m2 = probs(wires="a") tape.inv() # check that operation order is reversed assert tape.operations == [prep] + ops[::-1] # check that operations are inverted assert ops[0].inverse assert not ops[1].inverse assert ops[2].inverse # check that parameter order has reversed print(tape.get_parameters()) print([init_state, p[1], p[2], p[3], p[0]]) assert tape.get_parameters() == [init_state, p[1], p[2], p[3], p[0]]
def test_observable_with_no_measurement(self): """Test that an exception is raised if an observable is used without a measurement""" with pytest.raises(ValueError, match="does not have a measurement type specified"): with QuantumTape() as tape: qml.RX(0.5, wires=0) qml.Hermitian(np.array([[0, 1], [1, 0]]), wires=1) expval(qml.PauliZ(wires=1)) with pytest.raises(ValueError, match="does not have a measurement type specified"): with QuantumTape() as tape: qml.RX(0.5, wires=0) qml.PauliX(wires=0) @ qml.PauliY(wires=1) expval(qml.PauliZ(wires=1))
def test_multiple_contexts(self): """Test multiple contexts with a single tape.""" ops = [] obs = [] with QuantumTape() as tape: ops += [qml.RX(0.432, wires=0)] a = qml.Rot(0.543, 0, 0.23, wires=1) b = qml.CNOT(wires=[2, "a"]) with tape: ops += [qml.RX(0.133, wires=0)] obs += [qml.PauliX(wires="a")] expval(obs[0]) obs += [probs(wires=[0, "a"])] assert len(tape.queue) == 5 assert tape.operations == ops assert tape.observables == obs assert tape.output_dim == 5 assert a not in tape.operations assert b not in tape.operations assert tape.wires == qml.wires.Wires([0, "a"])
def test_depth_expansion(self): """Test expanding with depth=2""" with QuantumTape() as tape: # Will be decomposed into PauliX(0), PauliX(0) # Each PauliX will then be decomposed into PhaseShift, RX, PhaseShift. qml.BasisState(np.array([1, 1]), wires=[0, "a"]) with QuantumTape() as tape2: # will be decomposed into a RZ, RY, RZ qml.Rot(0.543, 0.1, 0.4, wires=0) qml.CNOT(wires=[0, "a"]) qml.RY(0.2, wires="a") probs(wires=0), probs(wires="a") new_tape = tape.expand(depth=2) assert len(new_tape.operations) == 11
def cost(a, device): with AutogradInterface.apply(QuantumTape()) as qtape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) expval(qml.PauliZ(0)) qtape.jacobian_options = {"h": 1e-8, "order": 2} return qtape.execute(dev)
def test_interface_str(self): """Test that the interface string is correctly identified as autograd""" with AutogradInterface.apply(QuantumTape()) as tape: qml.RX(0.5, wires=0) expval(qml.PauliX(0)) assert tape.interface == "autograd" assert isinstance(tape, AutogradInterface)
def cost(x, device): with AutogradInterface.apply(QuantumTape()) as tape: qml.Hadamard(wires=[0]) qml.CNOT(wires=[0, 1]) sample(qml.PauliZ(0)) sample(qml.PauliX(1)) return tape.execute(device)
def test_state_preparation_error(self): """Test that an exception is raised if a state preparation comes after a quantum operation""" with pytest.raises(ValueError, match="must occur prior to any quantum"): with QuantumTape() as tape: B = qml.PauliX(wires=0) qml.BasisState(np.array([0, 1]), wires=[0, 1])
def cost(x, device): with QuantumTape() as tape: qml.Hadamard(wires=[0]) qml.CNOT(wires=[0, 1]) sample(qml.PauliZ(0)) sample(qml.PauliX(1)) return tape.execute(device)
def get_tape(caching): """Creates a simple quantum tape""" with QuantumTape(caching=caching) as tape: qml.QubitUnitary(np.eye(2), wires=0) qml.RX(0.1, wires=0) qml.RX(0.2, wires=1) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(wires=1)) return tape