def test_compile_jax(self, diff_method): """Test QNode and gradient 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) original_qnode = qml.QNode(qfunc, dev, interface="jax", diff_method=diff_method) transformed_qnode = qml.QNode( transformed_qfunc, dev, interface="jax", diff_method=diff_method ) x = jnp.array([0.1, 0.2, 0.3], dtype=jnp.float64) params = jnp.ones((2, 3), dtype=jnp.float64) # Check that the numerical output is the same assert qml.math.allclose(original_qnode(x, params), transformed_qnode(x, params)) # Check that the gradient is the same assert qml.math.allclose( jax.grad(original_qnode, argnums=(1))(x, params), jax.grad(transformed_qnode, argnums=(1))(x, params), atol=1e-7, ) # Check operation list ops = transformed_qnode.qtape.operations compare_operation_lists(ops, expected_op_list, expected_wires_list)
def test_compile_mixed_tape_qfunc_transform(self): """Test that we can interchange tape and qfunc transforms.""" wires = [0, 1, 2] qfunc = build_qfunc(wires) dev = qml.device("default.qubit", wires=wires) pipeline = [ commute_controlled(direction="right").tape_fn, cancel_inverses, merge_rotations().tape_fn, ] transformed_qfunc = compile(pipeline=pipeline)(qfunc) transformed_qnode = qml.QNode(transformed_qfunc, dev) transformed_result = transformed_qnode(0.3, 0.4, 0.5) names_expected = ["Hadamard", "CNOT", "RX", "CY", "PauliY"] wires_expected = [ Wires(wires[0]), Wires([wires[2], wires[1]]), Wires(wires[0]), Wires([wires[1], wires[2]]), Wires(wires[2]), ] compare_operation_lists(transformed_qnode.qtape.operations, names_expected, wires_expected)
def test_compile_torch(self): """Test QNode and gradient in torch interface.""" torch = pytest.importorskip("torch", minversion="1.8") original_qnode = qml.QNode(qfunc, dev, interface="torch", diff_method="parameter-shift") transformed_qnode = qml.QNode( transformed_qfunc, dev, interface="torch", diff_method="parameter-shift" ) original_x = torch.tensor([0.3, -0.2, 0.8], requires_grad=False) original_params = torch.ones((2, 3), requires_grad=True) transformed_x = torch.tensor([0.3, -0.2, 0.8], requires_grad=False) transformed_params = torch.ones((2, 3), requires_grad=True) original_result = original_qnode(original_x, original_params) transformed_result = transformed_qnode(transformed_x, transformed_params) # Check that the numerical output is the same assert qml.math.allclose(original_result, transformed_result) # Check that the gradient is the same original_result.backward() transformed_result.backward() assert qml.math.allclose(original_params.grad, transformed_params.grad) # Check operation list ops = transformed_qnode.qtape.operations compare_operation_lists(ops, expected_op_list, expected_wires_list)
def test_compile_tf(self, diff_method): """Test QNode and gradient in tensorflow interface.""" tf = pytest.importorskip("tensorflow") original_qnode = qml.QNode(qfunc, dev, interface="tf", diff_method=diff_method) transformed_qnode = qml.QNode( transformed_qfunc, dev, interface="tf", diff_method=diff_method ) original_x = tf.Variable([0.8, -0.6, 0.4]) original_params = tf.Variable(tf.ones((2, 3))) transformed_x = tf.Variable([0.8, -0.6, 0.4]) transformed_params = tf.Variable(tf.ones((2, 3))) original_result = original_qnode(original_x, original_params) transformed_result = transformed_qnode(transformed_x, transformed_params) # Check that the numerical output is the same assert qml.math.allclose(original_result, transformed_result) # Check that the gradient is the same with tf.GradientTape() as tape: loss = original_qnode(original_x, original_params) original_grad = tape.gradient(loss, original_params) with tf.GradientTape() as tape: loss = transformed_qnode(transformed_x, transformed_params) transformed_grad = tape.gradient(loss, transformed_params) assert qml.math.allclose(original_grad, transformed_grad) # Check operation list ops = transformed_qnode.qtape.operations compare_operation_lists(ops, expected_op_list, expected_wires_list)
def test_compile_multiple_passes(self, wires): """Test that running multiple passes produces the correct results.""" qfunc = build_qfunc(wires) dev = qml.device("default.qubit", wires=Wires(wires)) qnode = qml.QNode(qfunc, dev) # Rotation merging will not occur at all until commuting gates are # pushed through pipeline = [merge_rotations, commute_controlled(direction="left"), cancel_inverses] transformed_qfunc = compile(pipeline=pipeline, num_passes=2)(qfunc) transformed_qnode = qml.QNode(transformed_qfunc, dev) original_result = qnode(0.3, 0.4, 0.5) transformed_result = transformed_qnode(0.3, 0.4, 0.5) assert np.allclose(original_result, transformed_result) names_expected = ["Hadamard", "CNOT", "RX", "PauliY", "CY"] wires_expected = [ Wires(wires[0]), Wires([wires[2], wires[1]]), Wires(wires[0]), Wires(wires[2]), Wires([wires[1], wires[2]]), ] compare_operation_lists(transformed_qnode.qtape.operations, names_expected, wires_expected)
def test_compile_template(self): """Test that functions with templates are correctly expanded and compiled.""" # Push commuting gates to the right and merging rotations gives a circuit # with alternating RX and CNOT gates def qfunc(x, params): qml.templates.AngleEmbedding(x, wires=range(3)) qml.templates.BasicEntanglerLayers(params, wires=range(3)) return qml.expval(qml.PauliZ(wires=2)) dev = qml.device("default.qubit", wires=3) qnode = qml.QNode(qfunc, dev) pipeline = [commute_controlled, merge_rotations] transformed_qfunc = compile(pipeline=pipeline)(qfunc) transformed_qnode = qml.QNode(transformed_qfunc, dev) x = np.array([0.1, 0.2, 0.3]) params = np.ones((2, 3)) original_result = qnode(x, params) transformed_result = transformed_qnode(x, params) assert np.allclose(original_result, transformed_result) names_expected = ["RX", "CNOT"] * 6 wires_expected = [ Wires(0), Wires([0, 1]), Wires(1), Wires([1, 2]), Wires(2), Wires([2, 0]), ] * 2 compare_operation_lists(transformed_qnode.qtape.operations, names_expected, wires_expected)
def test_compile_pipeline_with_non_default_arguments(self, wires): """Test that using non-default arguments returns the correct results.""" qfunc = build_qfunc(wires) dev = qml.device("default.qubit", wires=Wires(wires)) qnode = qml.QNode(qfunc, dev) pipeline = [ commute_controlled(direction="left"), cancel_inverses, merge_rotations(atol=1e-6), ] transformed_qfunc = compile(pipeline=pipeline)(qfunc) transformed_qnode = qml.QNode(transformed_qfunc, dev) original_result = qnode(0.3, 0.4, 0.5) transformed_result = transformed_qnode(0.3, 0.4, 0.5) assert np.allclose(original_result, transformed_result) names_expected = ["Hadamard", "CNOT", "RX", "PauliY", "CY"] wires_expected = [ Wires(wires[0]), Wires([wires[2], wires[1]]), Wires(wires[0]), Wires(wires[2]), Wires([wires[1], wires[2]]), ] compare_operation_lists(transformed_qnode.qtape.operations, names_expected, wires_expected)
def test_compile_default_pipeline(self, wires): """Test that the default pipeline returns the correct results.""" qfunc = build_qfunc(wires) dev = qml.device("default.qubit", wires=Wires(wires)) qnode = qml.QNode(qfunc, dev) transformed_qfunc = compile()(qfunc) transformed_qnode = qml.QNode(transformed_qfunc, dev) original_result = qnode(0.3, 0.4, 0.5) transformed_result = transformed_qnode(0.3, 0.4, 0.5) assert np.allclose(original_result, transformed_result) names_expected = ["Hadamard", "CNOT", "RX", "CY", "PauliY"] wires_expected = [ Wires(wires[0]), Wires([wires[2], wires[1]]), Wires(wires[0]), Wires([wires[1], wires[2]]), Wires(wires[2]), ] compare_operation_lists(transformed_qnode.qtape.operations, names_expected, wires_expected)
def test_compile_autograd(self, diff_method): """Test QNode and gradient in autograd interface.""" original_qnode = qml.QNode(qfunc, dev, interface="autograd", diff_method=diff_method) transformed_qnode = qml.QNode(transformed_qfunc, dev, interface="autograd", diff_method=diff_method) x = np.array([0.1, 0.2, 0.3], requires_grad=False) params = np.ones((2, 3)) # Check that the numerical output is the same assert qml.math.allclose(original_qnode(x, params), transformed_qnode(x, params)) # Check that the gradient is the same assert qml.math.allclose( qml.grad(original_qnode)(x, params), qml.grad(transformed_qnode)(x, params)) # Check operation list ops = transformed_qnode.qtape.operations compare_operation_lists(ops, expected_op_list, expected_wires_list)
def test_compile_decompose_into_basis_gates(self, wires): """Test that running multiple passes produces the correct results.""" qfunc = build_qfunc(wires) dev = qml.device("default.qubit", wires=Wires(wires)) qnode = qml.QNode(qfunc, dev) pipeline = [ commute_controlled(direction="left"), cancel_inverses, merge_rotations ] basis_set = ["CNOT", "RX", "RY", "RZ"] transformed_qfunc = compile(pipeline=pipeline, basis_set=basis_set)(qfunc) transformed_qnode = qml.QNode(transformed_qfunc, dev) original_result = qnode(0.3, 0.4, 0.5) transformed_result = transformed_qnode(0.3, 0.4, 0.5) assert np.allclose(original_result, transformed_result) names_expected = [ "RZ", "RX", "RZ", "RZ", "CNOT", "RX", "RZ", "RY", "RZ", "RY", "CNOT", "RY", "CNOT", ] wires_expected = [ Wires(wires[0]), Wires(wires[0]), Wires(wires[0]), Wires(wires[2]), Wires([wires[2], wires[1]]), Wires(wires[0]), Wires(wires[1]), Wires(wires[2]), Wires(wires[2]), Wires(wires[2]), Wires([wires[1], wires[2]]), Wires(wires[2]), Wires([wires[1], wires[2]]), ] compare_operation_lists(transformed_qnode.qtape.operations, names_expected, wires_expected)
def test_compile_empty_pipeline(self, wires): """Test that an empty pipeline returns the original function.""" qfunc = build_qfunc(wires) dev = qml.device("default.qubit", wires=wires) qnode = qml.QNode(qfunc, dev) transformed_qfunc = compile(pipeline=[])(qfunc) transformed_qnode = qml.QNode(transformed_qfunc, dev) original_result = qnode(0.3, 0.4, 0.5) transformed_result = transformed_qnode(0.3, 0.4, 0.5) assert np.allclose(original_result, transformed_result) names_expected = [op.name for op in qnode.qtape.operations] wires_expected = [op.wires for op in qnode.qtape.operations] compare_operation_lists(transformed_qnode.qtape.operations, names_expected, wires_expected)