def test_push_z_gates_left(self): """Test that Z-basis after before controlled-Z-type gates on controls *and* targets get pushed behind.""" def qfunc(): qml.CZ(wires=[0, 2]) qml.PauliZ(wires=2) qml.S(wires=0) qml.CNOT(wires=[0, 1]) qml.CRZ(0.5, wires=[0, 1]) qml.RZ(0.2, wires=2) qml.T(wires=0) qml.PauliZ(wires=0) transformed_qfunc = commute_controlled(direction="left")(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = [ "PauliZ", "S", "RZ", "T", "PauliZ", "CZ", "CNOT", "CRZ" ] wires_expected = [ Wires(2), Wires(0), Wires(2), Wires(0), Wires(0), Wires([0, 2]) ] + [Wires([0, 1])] * 2 compare_operation_lists(ops, names_expected, wires_expected)
def test_commute_controlled_torch(self): """Test QNode and gradient in torch interface.""" torch = pytest.importorskip("torch", minversion="1.8") original_qnode = qml.QNode(qfunc, dev, interface="torch") transformed_qnode = qml.QNode(transformed_qfunc, dev, interface="torch") original_input = torch.tensor([1.2, -0.35], requires_grad=True) transformed_input = torch.tensor([1.2, -0.35], requires_grad=True) original_result = original_qnode(original_input) transformed_result = transformed_qnode(transformed_input) # 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_input.grad, transformed_input.grad) # Check operation list ops = transformed_qnode.qtape.operations compare_operation_lists(ops, expected_op_list, expected_wires_list)
def test_commute_controlled_jax(self): """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") transformed_qnode = qml.QNode(transformed_qfunc, dev, interface="jax") input = jnp.array([0.3, 0.4], dtype=jnp.float64) # Check that the numerical output is the same assert qml.math.allclose(original_qnode(input), transformed_qnode(input)) # Check that the gradient is the same assert qml.math.allclose( jax.grad(original_qnode)(input), jax.grad(transformed_qnode)(input)) # Check operation list ops = transformed_qnode.qtape.operations compare_operation_lists(ops, expected_op_list, expected_wires_list)
def test_commute_controlled_tf(self): """Test QNode and gradient in tensorflow interface.""" tf = pytest.importorskip("tensorflow") original_qnode = qml.QNode(qfunc, dev, interface="tf") transformed_qnode = qml.QNode(transformed_qfunc, dev, interface="tf") original_input = tf.Variable([0.8, -0.6]) transformed_input = tf.Variable([0.8, -0.6]) original_result = original_qnode(original_input) transformed_result = transformed_qnode(transformed_input) # 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_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) 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_push_z_gates_right(self): """Test that Z-basis gates before controlled-Z-type gates on controls *and* targets get pushed ahead.""" def qfunc(): qml.PauliZ(wires=2) qml.S(wires=0) qml.CZ(wires=[0, 2]) qml.CNOT(wires=[0, 1]) qml.PhaseShift(0.2, wires=2) qml.T(wires=0) qml.PauliZ(wires=0) qml.CRZ(0.5, wires=[0, 1]) transformed_qfunc = commute_controlled()(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = [ "CZ", "PauliZ", "CNOT", "PhaseShift", "CRZ", "S", "T", "PauliZ" ] wires_expected = ( [Wires([0, 2]), Wires(2), Wires([0, 1]), Wires(2)] + [Wires([0, 1])] + [Wires(0)] * 3) compare_operation_lists(ops, names_expected, wires_expected)
def test_push_x_gates_right(self): """Test that X-basis gates before controlled-X-type gates on targets get pushed ahead.""" def qfunc(): qml.PauliX(wires=2) qml.CNOT(wires=[0, 2]) qml.RX(0.2, wires=2) qml.Toffoli(wires=[0, 1, 2]) qml.SX(wires=1) qml.PauliX(wires=1) qml.CRX(0.1, wires=[0, 1]) transformed_qfunc = commute_controlled()(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = [ "CNOT", "Toffoli", "PauliX", "RX", "CRX", "SX", "PauliX" ] wires_expected = [ Wires([0, 2]), Wires([0, 1, 2]), Wires(2), Wires(2), Wires([0, 1]), Wires(1), Wires(1), ] compare_operation_lists(ops, names_expected, wires_expected)
def test_two_qubits_cnot_opposite_direction(self): """Test that two adjacent CNOTs with the control/target flipped do NOT cancel.""" def qfunc(): qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[1, 0]) transformed_qfunc = cancel_inverses(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["CNOT"] * 2 wires_expected = [Wires([0, 1]), Wires([1, 0])] compare_operation_lists(ops, names_expected, wires_expected)
def test_two_qubits_no_inverse(self): """Test that a two-qubit circuit self-inverse on each qubit does not cancel.""" def qfunc(): qml.Hadamard(wires=0) qml.Hadamard(wires=1) transformed_qfunc = cancel_inverses(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["Hadamard"] * 2 wires_expected = [Wires(0), Wires(1)] compare_operation_lists(ops, names_expected, wires_expected)
def test_gate_blocked_different_basis(self, direction): """Test that gates do not get pushed through controlled gates whose target bases don't match.""" def qfunc(): qml.PauliZ(wires="b") qml.CNOT(wires=[2, "b"]) qml.PauliY(wires="b") transformed_qfunc = commute_controlled(direction=direction)(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["PauliZ", "CNOT", "PauliY"] wires_expected = [Wires("b"), Wires([2, "b"]), Wires("b")] compare_operation_lists(ops, names_expected, wires_expected)
def test_one_qubit_no_inverse(self): """Test that a one-qubit circuit with a gate in the way does not cancel the inverses.""" def qfunc(): qml.Hadamard(wires=0) qml.RZ(0.3, wires=0) qml.Hadamard(wires=0) transformed_qfunc = cancel_inverses(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["Hadamard", "RZ", "Hadamard"] wires_expected = [Wires(0)] * 3 compare_operation_lists(ops, names_expected, wires_expected)
def test_controlled_rotation_no_merge(self): """Test that adjacent controlled rotations on the same wires in different order don't merge.""" def qfunc(): qml.CRX(0.2, wires=["w1", "w2"]) qml.CRX(0.3, wires=["w2", "w1"]) transformed_qfunc = merge_rotations()(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["CRX", "CRX"] wires_expected = [Wires(["w1", "w2"]), Wires(["w2", "w1"])] compare_operation_lists(ops, names_expected, wires_expected) assert ops[0].parameters[0] == 0.2 assert ops[1].parameters[0] == 0.3
def test_three_qubits_inverse_after_cnot(self): """Test that a three-qubit circuit with a CNOT still allows cancellation.""" def qfunc(): qml.Hadamard(wires=0) qml.PauliX(wires=1) qml.CNOT(wires=[0, 2]) qml.RZ(0.5, wires=2) qml.PauliX(wires=1) transformed_qfunc = cancel_inverses(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["Hadamard", "CNOT", "RZ"] wires_expected = [Wires(0), Wires([0, 2]), Wires(2)] compare_operation_lists(ops, names_expected, wires_expected)
def test_gate_with_no_basis(self, direction): """Test that gates with no basis specified are ignored.""" def qfunc(): qml.PauliX(wires=2) qml.ControlledQubitUnitary(np.array([[0, 1], [1, 0]]), control_wires=0, wires=2) qml.PauliX(wires=2) transformed_qfunc = commute_controlled(direction=direction)(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["PauliX", "ControlledQubitUnitary", "PauliX"] wires_expected = [Wires(2), Wires([0, 2]), Wires(2)] compare_operation_lists(ops, names_expected, wires_expected)
def test_one_qubit_rotation_blocked(self): """Test that rotations on one-qubit separated by a "blocking" operation don't merge.""" def qfunc(): qml.RX(0.5, wires=0) qml.Hadamard(wires=0) qml.RX(0.4, wires=0) transformed_qfunc = merge_rotations()(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["RX", "Hadamard", "RX"] wires_expected = [Wires(0)] * 3 compare_operation_lists(ops, names_expected, wires_expected) assert ops[0].parameters[0] == 0.5 assert ops[2].parameters[0] == 0.4
def test_two_qubits_rotation_blocked(self): """Test that rotations on a two-qubit system separated by a "blocking" operation don't merge.""" def qfunc(): qml.RX(-0.42, wires=0) qml.CNOT(wires=[0, 1]) qml.RX(0.8, wires=0) transformed_qfunc = merge_rotations()(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["RX", "CNOT", "RX"] wires_expected = [Wires(0), Wires([0, 1]), Wires(0)] compare_operation_lists(ops, names_expected, wires_expected) assert ops[0].parameters[0] == -0.42 assert ops[2].parameters[0] == 0.8
def test_undo_swaps_autograd(self): """Test QNode and gradient in autograd interface.""" original_qnode = qml.QNode(qfunc, dev) transformed_qnode = qml.QNode(transformed_qfunc, dev) input = np.array([0.1, 0.2], requires_grad=True) # Check that the numerical output is the same assert qml.math.allclose(original_qnode(input), transformed_qnode(input)) # Check that the gradient is the same assert qml.math.allclose( qml.grad(original_qnode)(input), qml.grad(transformed_qnode)(input) ) # Check operation list ops = transformed_qnode.qtape.operations compare_operation_lists(ops, expected_op_list, expected_wires_list)
def test_dont_push_x_gates(self, direction): """Test that X-basis gates before controlled-X-type gates on controls don't get pushed.""" def qfunc(): qml.PauliX(wires="a") qml.CNOT(wires=["a", "c"]) qml.RX(0.2, wires="a") qml.Toffoli(wires=["c", "a", "b"]) transformed_qfunc = commute_controlled(direction=direction)(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["PauliX", "CNOT", "RX", "Toffoli"] wires_expected = [ Wires("a"), Wires(["a", "c"]), Wires("a"), Wires(["c", "a", "b"]) ] compare_operation_lists(ops, names_expected, wires_expected)
def test_three_qubits_toffolis(self): """Test that Toffolis on different permutations of wires cancel correctly.""" def qfunc(): # These two will cancel qml.Toffoli(wires=["a", "b", "c"]) qml.Toffoli(wires=["b", "a", "c"]) # These three will not cancel qml.Toffoli(wires=["a", "b", "c"]) qml.Toffoli(wires=["a", "c", "b"]) qml.Toffoli(wires=["a", "c", "d"]) transformed_qfunc = cancel_inverses(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["Toffoli"] * 3 wires_expected = [ Wires(["a", "b", "c"]), Wires(["a", "c", "b"]), Wires(["a", "c", "d"]) ] compare_operation_lists(ops, names_expected, wires_expected)
def test_two_qubits_merge_gate_subset(self): """Test that specifying a subset of operations to include merges correctly.""" def qfunc(): qml.CRX(0.1, wires=[0, 1]) qml.CRX(0.2, wires=[0, 1]) qml.RY(0.3, wires=["a"]) qml.RY(0.5, wires=["a"]) qml.RX(-0.5, wires=[2]) qml.RX(0.2, wires=[2]) transformed_qfunc = merge_rotations(include_gates=["RX", "CRX"])(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["CRX", "RY", "RY", "RX"] wires_expected = [Wires([0, 1]), Wires("a"), Wires("a"), Wires(2)] compare_operation_lists(ops, names_expected, wires_expected) assert qml.math.isclose(ops[0].parameters[0], 0.3) assert qml.math.isclose(ops[1].parameters[0], 0.3) assert qml.math.isclose(ops[2].parameters[0], 0.5) assert qml.math.isclose(ops[3].parameters[0], -0.3)
def test_dont_push_y_gates(self, direction): """Test that Y-basis gates next to controlled-Y-type gates on controls don't get pushed.""" def qfunc(): qml.CRY(-0.2, wires=["a", 2]) qml.PauliY(wires="a") qml.CNOT(wires=[1, 2]) qml.CY(wires=["a", 1]) qml.RY(0.3, wires="a") transformed_qfunc = commute_controlled()(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["CRY", "PauliY", "CNOT", "CY", "RY"] wires_expected = [ Wires(["a", 2]), Wires("a"), Wires([1, 2]), Wires(["a", 1]), Wires("a") ] compare_operation_lists(ops, names_expected, wires_expected)
def test_push_y_gates_left(self): """Test that Y-basis gates after controlled-Y-type gates on targets get pushed behind.""" def qfunc(): qml.CRY(-0.5, wires=["a", 2]) qml.PauliY(wires=2) qml.CNOT(wires=[1, 2]) qml.CY(wires=["a", 1]) qml.RY(0.3, wires=1) transformed_qfunc = commute_controlled(direction="left")(qfunc) ops = qml.transforms.make_tape(transformed_qfunc)().operations names_expected = ["PauliY", "CRY", "CNOT", "RY", "CY"] wires_expected = [ Wires(2), Wires(["a", 2]), Wires([1, 2]), Wires(1), Wires(["a", 1]) ] compare_operation_lists(ops, names_expected, wires_expected)