def test_gradient_unitary_to_rot(self, rot_angles, diff_method): """Tests differentiability in autograd interface.""" def qfunc_with_qubit_unitary(angles): z = angles[0] x = angles[1] Z_mat = np.array([[np.exp(-1j * z / 2), 0.0], [0.0, np.exp(1j * z / 2)]]) c = np.cos(x / 2) s = np.sin(x / 2) * 1j X_mat = np.array([[c, -s], [-s, c]]) qml.Hadamard(wires="a") qml.QubitUnitary(Z_mat, wires="a") qml.QubitUnitary(X_mat, wires="b") qml.CNOT(wires=["b", "a"]) return qml.expval(qml.PauliX(wires="a")) dev = qml.device("default.qubit", wires=["a", "b"]) original_qnode = qml.QNode(original_qfunc_for_grad, dev, diff_method=diff_method) transformed_qfunc = unitary_to_rot(qfunc_with_qubit_unitary) transformed_qnode = qml.QNode(transformed_qfunc, dev, diff_method=diff_method) input = np.array(rot_angles, requires_grad=True) assert qml.math.allclose(original_qnode(input), transformed_qnode(input)) original_grad = qml.grad(original_qnode)(input) transformed_grad = qml.grad(transformed_qnode)(input) assert qml.math.allclose(original_grad, transformed_grad)
def test_gradient_unitary_to_rot_two_qubit(self, diff_method): """Tests differentiability in autograd interface.""" U0 = np.array(test_two_qubit_unitaries[0], requires_grad=False, dtype=np.complex128) U1 = np.array(test_two_qubit_unitaries[1], requires_grad=False, dtype=np.complex128) def two_qubit_decomp_qnode(x, y, z): qml.RX(x, wires=0) qml.RY(y, wires=1) qml.QubitUnitary(U0, wires=[0, 1]) qml.RZ(z, wires=2) qml.QubitUnitary(U1, wires=[1, 2]) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2)) x = np.array(0.1, requires_grad=True) y = np.array(0.2, requires_grad=True) z = np.array(0.3, requires_grad=True) dev = qml.device("default.qubit", wires=3) original_qnode = qml.QNode(two_qubit_decomp_qnode, device=dev, diff_method=diff_method) transformed_qfunc = unitary_to_rot(two_qubit_decomp_qnode) transformed_qnode = qml.QNode(transformed_qfunc, dev, diff_method=diff_method) assert qml.math.allclose(original_qnode(x, y, z), transformed_qnode(x, y, z)) # 3 normal operations + 10 for the first decomp and 2 for the second assert len(transformed_qnode.qtape.operations) == 15 original_grad = qml.grad(original_qnode)(x, y, z) transformed_grad = qml.grad(transformed_qnode)(x, y, z) assert qml.math.allclose(original_grad, transformed_grad, atol=1e-6)
def test_unitary_to_rot_jax(self, U, expected_gate, expected_params): """Test that the transform works in the JAX interface.""" jax = pytest.importorskip("jax") # Enable float64 support from jax.config import config remember = config.read("jax_enable_x64") config.update("jax_enable_x64", True) U = jax.numpy.array(U, dtype=jax.numpy.complex64) transformed_qfunc = unitary_to_rot(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)(U).operations assert len(ops) == 3 assert isinstance(ops[0], qml.Hadamard) assert ops[0].wires == Wires("a") assert isinstance(ops[1], expected_gate) assert ops[1].wires == Wires("a") assert qml.math.allclose([jax.numpy.asarray(x) for x in ops[1].parameters], expected_params) assert isinstance(ops[2], qml.CNOT) assert ops[2].wires == Wires(["b", "a"])
def test_gradient_unitary_to_rot_torch(self, rot_angles): """Tests differentiability in torch interface. Torch interface doesn't use backprop so we test only with parameter-shift.""" torch = pytest.importorskip("torch", minversion="1.8") def qfunc_with_qubit_unitary(angles): z = angles[0] x = angles[1] # Had to do this in order to make a torch tensor of torch tensors Z_mat = torch.stack([ torch.exp(-1j * z / 2), torch.tensor(0.0), torch.tensor(0.0), torch.exp(1j * z / 2), ]).reshape(2, 2) # Variables need to be complex c = torch.cos(x / 2).type(torch.complex64) s = torch.sin(x / 2) * 1j X_mat = torch.stack([c, -s, -s, c]).reshape(2, 2) qml.Hadamard(wires="a") qml.QubitUnitary(Z_mat, wires="a") qml.QubitUnitary(X_mat, wires="b") qml.CNOT(wires=["b", "a"]) return qml.expval(qml.PauliX(wires="a")) original_qnode = qml.QNode(original_qfunc_for_grad, dev, interface="torch", diff_method="parameter-shift") original_input = torch.tensor(rot_angles, dtype=torch.float64, requires_grad=True) original_result = original_qnode(original_input) transformed_qfunc = unitary_to_rot(qfunc_with_qubit_unitary) transformed_qnode = qml.QNode(transformed_qfunc, dev, interface="torch", diff_method="parameter-shift") transformed_input = torch.tensor(rot_angles, dtype=torch.float64, requires_grad=True) transformed_result = transformed_qnode(transformed_input) assert qml.math.allclose(original_result, transformed_result) original_result.backward() transformed_result.backward() assert qml.math.allclose(original_input.grad, transformed_input.grad)
def test_gradient_unitary_to_rot_tf(self, rot_angles, diff_method): """Tests differentiability in tensorflow interface.""" tf = pytest.importorskip("tensorflow") def qfunc_with_qubit_unitary(angles): z = tf.cast(angles[0], tf.complex128) x = tf.cast(angles[1], tf.complex128) c = tf.cos(x / 2) s = tf.sin(x / 2) * 1j Z_mat = tf.convert_to_tensor([[tf.exp(-1j * z / 2), 0.0], [0.0, tf.exp(1j * z / 2)]]) X_mat = tf.convert_to_tensor([[c, -s], [-s, c]]) qml.Hadamard(wires="a") qml.QubitUnitary(Z_mat, wires="a") qml.QubitUnitary(X_mat, wires="b") qml.CNOT(wires=["b", "a"]) return qml.expval(qml.PauliX(wires="a")) original_qnode = qml.QNode(original_qfunc_for_grad, dev, interface="tf", diff_method=diff_method) original_input = tf.Variable(rot_angles, dtype=tf.float64) original_result = original_qnode(original_input) transformed_qfunc = unitary_to_rot(qfunc_with_qubit_unitary) transformed_qnode = qml.QNode(transformed_qfunc, dev, interface="tf", diff_method=diff_method) transformed_input = tf.Variable(rot_angles, dtype=tf.float64) transformed_result = transformed_qnode(transformed_input) assert qml.math.allclose(original_result, transformed_result) with tf.GradientTape() as tape: loss = original_qnode(original_input) original_grad = tape.gradient(loss, original_input) with tf.GradientTape() as tape: loss = transformed_qnode(transformed_input) transformed_grad = tape.gradient(loss, transformed_input) # For 64bit values, need to slightly increase the tolerance threshold assert qml.math.allclose(original_grad, transformed_grad, atol=1e-7)
def test_gradient_unitary_to_rot_tf_two_qubits(self, diff_method): """Tests differentiability in tensorflow interface.""" tf = pytest.importorskip("tensorflow") # We have to mark these as constant, otherwise it will try to # differentiate with respect to them. U0 = tf.constant(test_two_qubit_unitaries[0], dtype=tf.complex128) U1 = tf.constant(test_two_qubit_unitaries[1], dtype=tf.complex128) def two_qubit_decomp_qnode(x): qml.RX(x, wires=0) qml.QubitUnitary(U0, wires=[0, 1]) qml.QubitUnitary(U1, wires=[1, 2]) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2)) x = tf.Variable(0.1, dtype=tf.float64) transformed_x = tf.Variable(0.1, dtype=tf.float64) dev = qml.device("default.qubit", wires=3) original_qnode = qml.QNode( two_qubit_decomp_qnode, dev, interface="tf", diff_method=diff_method ) original_result = original_qnode(x) transformed_qfunc = unitary_to_rot(two_qubit_decomp_qnode) transformed_qnode = qml.QNode( transformed_qfunc, dev, interface="tf", diff_method=diff_method ) transformed_result = transformed_qnode(transformed_x) assert qml.math.allclose(original_result, transformed_result) assert len(transformed_qnode.qtape.operations) == 13 with tf.GradientTape() as tape: loss = original_qnode(x) original_grad = tape.gradient(loss, x) with tf.GradientTape() as tape: loss = transformed_qnode(transformed_x) transformed_grad = tape.gradient(loss, transformed_x) # For 64bit values, need to slightly increase the tolerance threshold assert qml.math.allclose(original_grad, transformed_grad, atol=1e-7)
def test_gradient_unitary_to_rot_jax(self, rot_angles, diff_method): """Tests differentiability in jax interface.""" jax = pytest.importorskip("jax") from jax import numpy as jnp # Enable float64 support from jax.config import config remember = config.read("jax_enable_x64") config.update("jax_enable_x64", True) def qfunc_with_qubit_unitary(angles): z = angles[0] x = angles[1] Z_mat = jnp.array([[jnp.exp(-1j * z / 2), 0.0], [0.0, jnp.exp(1j * z / 2)]]) c = jnp.cos(x / 2) s = jnp.sin(x / 2) * 1j X_mat = jnp.array([[c, -s], [-s, c]]) qml.Hadamard(wires="a") qml.QubitUnitary(Z_mat, wires="a") qml.QubitUnitary(X_mat, wires="b") qml.CNOT(wires=["b", "a"]) return qml.expval(qml.PauliX(wires="a")) # Setting the dtype to complex64 causes the gradients to be complex... input = jnp.array(rot_angles, dtype=jnp.float64) dev = qml.device("default.qubit", wires=["a", "b"]) original_qnode = qml.QNode( original_qfunc_for_grad, dev, interface="jax", diff_method=diff_method ) original_result = original_qnode(input) transformed_qfunc = unitary_to_rot(qfunc_with_qubit_unitary) transformed_qnode = qml.QNode( transformed_qfunc, dev, interface="jax", diff_method=diff_method ) transformed_result = transformed_qnode(input) assert qml.math.allclose(original_result, transformed_result) original_grad = jax.grad(original_qnode)(input) transformed_grad = jax.grad(transformed_qnode)(input) assert qml.math.allclose(original_grad, transformed_grad, atol=1e-7)
def test_unitary_to_rot(self, U, expected_gate, expected_params): """Test that the transform works in the autograd interface.""" transformed_qfunc = unitary_to_rot(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)(U).operations assert len(ops) == 3 assert isinstance(ops[0], qml.Hadamard) assert ops[0].wires == Wires("a") assert isinstance(ops[1], expected_gate) assert ops[1].wires == Wires("a") assert qml.math.allclose(ops[1].parameters, expected_params) assert isinstance(ops[2], qml.CNOT) assert ops[2].wires == Wires(["b", "a"])
def test_gradient_unitary_to_rot_torch_two_qubit(self): """Tests differentiability in torch interface.""" torch = pytest.importorskip("torch") U0 = torch.tensor(test_two_qubit_unitaries[0], requires_grad=False, dtype=torch.complex128) U1 = torch.tensor(test_two_qubit_unitaries[1], requires_grad=False, dtype=torch.complex128) def two_qubit_decomp_qnode(x, y, z): qml.RX(x, wires=0) qml.RY(y, wires=1) qml.QubitUnitary(U0, wires=[0, 1]) qml.RZ(z, wires=2) qml.QubitUnitary(U1, wires=[1, 2]) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2)) x = torch.tensor(0.1, requires_grad=True) y = torch.tensor(0.2, requires_grad=True) z = torch.tensor(0.3, requires_grad=True) transformed_x = torch.tensor(0.1, requires_grad=True) transformed_y = torch.tensor(0.2, requires_grad=True) transformed_z = torch.tensor(0.3, requires_grad=True) dev = qml.device("default.qubit", wires=3) original_qnode = qml.QNode( two_qubit_decomp_qnode, device=dev, interface="torch", diff_method="parameter-shift" ) transformed_qfunc = unitary_to_rot(two_qubit_decomp_qnode) transformed_qnode = qml.QNode( transformed_qfunc, dev, interface="torch", diff_method="parameter-shift" ) original_result = original_qnode(x, y, z) transformed_result = transformed_qnode(transformed_x, transformed_y, transformed_z) assert qml.math.allclose(original_result, transformed_result) assert len(transformed_qnode.qtape.operations) == 15 original_result.backward() transformed_result.backward() assert qml.math.allclose(x.grad, transformed_x.grad)
def test_unitary_to_rot_multiple_two_qubit(num_reps): """Test that numerous two-qubit unitaries can be decomposed sequentially.""" dev = qml.device("default.qubit", wires=2) U = np.array(test_two_qubit_unitaries[1], dtype=np.complex128) def my_circuit(): for rep in range(num_reps): qml.QubitUnitary(U, wires=[0, 1]) return qml.expval(qml.PauliZ(0)) original_qnode = qml.QNode(my_circuit, dev) transformed_qnode = qml.QNode(unitary_to_rot(my_circuit), dev) original_matrix = qml.transforms.get_unitary_matrix(original_qnode)() transformed_matrix = qml.transforms.get_unitary_matrix(transformed_qnode)() assert check_matrix_equivalence(original_matrix, transformed_matrix, atol=1e-7)
def test_gradient_unitary_to_rot_two_qubit_jax(self): """Tests differentiability in jax interface.""" jax = pytest.importorskip("jax") from jax import numpy as jnp from jax.config import config remember = config.read("jax_enable_x64") config.update("jax_enable_x64", True) U0 = jnp.array(test_two_qubit_unitaries[0], dtype=jnp.complex128) U1 = jnp.array(test_two_qubit_unitaries[1], dtype=jnp.complex128) def two_qubit_decomp_qnode(x): qml.RX(x, wires=0) qml.QubitUnitary(U0, wires=[0, 1]) qml.QubitUnitary(U1, wires=[1, 2]) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2)) x = jnp.array(0.1, dtype=jnp.float64) dev = qml.device("default.qubit", wires=3) original_qnode = qml.QNode( two_qubit_decomp_qnode, device=dev, interface="jax", diff_method="backprop" ) transformed_qfunc = unitary_to_rot(two_qubit_decomp_qnode) transformed_qnode = qml.QNode( transformed_qfunc, dev, interface="jax", diff_method="backprop" ) assert qml.math.allclose(original_qnode(x), transformed_qnode(x)) # 3 normal operations + 10 for the first decomp and 2 for the second assert len(transformed_qnode.qtape.operations) == 13 original_grad = jax.grad(original_qnode, argnums=(0))(x) transformed_grad = jax.grad(transformed_qnode, argnums=(0))(x) assert qml.math.allclose(original_grad, transformed_grad, atol=1e-6)
def test_unitary_to_rot_too_big_unitary(self): """Test that the transform ignores QubitUnitary instances that are too big to decompose.""" tof = qml.Toffoli(wires=[0, 1, 2]).matrix def qfunc(): qml.QubitUnitary(H, wires="a") qml.QubitUnitary(tof, wires=["a", "b", "c"]) transformed_qfunc = unitary_to_rot(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations assert len(ops) == 2 assert ops[0].name == "Rot" assert ops[0].wires == Wires("a") assert ops[1].name == "QubitUnitary" assert ops[1].wires == Wires(["a", "b", "c"])
def test_unitary_to_rot_tf(self, U, expected_gate, expected_params): """Test that the transform works in the Tensorflow interface.""" tf = pytest.importorskip("tensorflow") U = tf.Variable(U, dtype=tf.complex64) transformed_qfunc = unitary_to_rot(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)(U).operations assert len(ops) == 3 assert isinstance(ops[0], qml.Hadamard) assert ops[0].wires == Wires("a") assert isinstance(ops[1], expected_gate) assert ops[1].wires == Wires("a") assert qml.math.allclose(qml.math.unwrap(ops[1].parameters), expected_params) assert isinstance(ops[2], qml.CNOT) assert ops[2].wires == Wires(["b", "a"])
def test_unitary_to_rot_torch(self, U, expected_gate, expected_params): """Test that the transform works in the torch interface.""" torch = pytest.importorskip("torch") U = torch.tensor(U, dtype=torch.complex64) transformed_qfunc = unitary_to_rot(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)(U).operations assert len(ops) == 3 assert isinstance(ops[0], qml.Hadamard) assert ops[0].wires == Wires("a") assert isinstance(ops[1], expected_gate) assert ops[1].wires == Wires("a") assert qml.math.allclose([x.detach() for x in ops[1].parameters], expected_params) assert isinstance(ops[2], qml.CNOT) assert ops[2].wires == Wires(["b", "a"])