def test_get_unitary_matrix_interface_autograd(): """Test with autograd interface""" dev = qml.device("default.qubit", wires=3) def circuit(theta): qml.RZ(theta[0], wires=0) qml.RZ(theta[1], wires=1) qml.CRY(theta[2], wires=[1, 2]) return qml.expval(qml.PauliZ(1)) # set qnode interface qnode = qml.QNode(circuit, dev, interface="autograd") get_matrix = get_unitary_matrix(qnode) # set input parameters theta = np.array([0.1, 0.2, 0.3], requires_grad=True) matrix = get_matrix(theta) # expected matrix matrix1 = np.kron( qml.RZ(theta[0], wires=0).matrix, np.kron(qml.RZ(theta[1], wires=1).matrix, I) ) matrix2 = np.kron(I, qml.CRY(theta[2], wires=[1, 2]).matrix) expected_matrix = matrix2 @ matrix1 assert np.allclose(matrix, expected_matrix)
def test_get_unitary_matrix_CNOT(target_wire): """Test CNOT: 2-qubit gate with different target wires, some non-adjacent.""" wires = [0, 1, 2, 3, 4] def testcircuit(): qml.CNOT(wires=[1, target_wire]) get_matrix = get_unitary_matrix(testcircuit, wires) matrix = get_matrix() # test the matrix operation on a state state0 = [1, 0] state1 = [0, 1] teststate = reduce(np.kron, [state1, state1, state1, state1, state1]) if target_wire == 0: expected_state = reduce(np.kron, [state0, state1, state1, state1, state1]) elif target_wire == 2: expected_state = reduce(np.kron, [state1, state1, state0, state1, state1]) elif target_wire == 3: expected_state = reduce(np.kron, [state1, state1, state1, state0, state1]) elif target_wire == 4: expected_state = reduce(np.kron, [state1, state1, state1, state1, state0]) obtained_state = matrix @ teststate assert np.allclose(obtained_state, expected_state)
def test_get_unitary_matrix_interface_tf(): """Test with tensorflow interface""" tf = pytest.importorskip("tensorflow") dev = qml.device("default.qubit", wires=3) def circuit(beta, theta): qml.RZ(beta, wires=0) qml.RZ(theta[0], wires=1) qml.CRY(theta[1], wires=[1, 2]) return qml.expval(qml.PauliZ(1)) # set qnode interface qnode_tensorflow = qml.QNode(circuit, dev, interface="tf") get_matrix = get_unitary_matrix(qnode_tensorflow) beta = 0.1 # input tensorflow parameters theta = tf.Variable([0.2, 0.3]) matrix = get_matrix(beta, theta) # expected matrix theta_np = theta.numpy() matrix1 = np.kron(qml.RZ(beta, wires=0).matrix, np.kron(qml.RZ(theta_np[0], wires=1).matrix, I)) matrix2 = np.kron(I, qml.CRY(theta_np[1], wires=[1, 2]).matrix) expected_matrix = matrix2 @ matrix1 assert np.allclose(matrix, expected_matrix)
def test_get_unitary_matrix_interface_torch(): """Test with torch interface""" torch = pytest.importorskip("torch", minversion="1.8") dev = qml.device("default.qubit", wires=3) def circuit(theta): qml.RZ(theta[0], wires=0) qml.RZ(theta[1], wires=1) qml.CRY(theta[2], wires=[1, 2]) return qml.expval(qml.PauliZ(1)) # set qnode interface qnode_torch = qml.QNode(circuit, dev, interface="torch") get_matrix = get_unitary_matrix(qnode_torch) # input torch parameters theta = torch.tensor([0.1, 0.2, 0.3]) matrix = get_matrix(theta) # expected matrix matrix1 = np.kron( qml.RZ(theta[0], wires=0).matrix, np.kron(qml.RZ(theta[1], wires=1).matrix, I) ) matrix2 = np.kron(I, qml.CRY(theta[2], wires=[1, 2]).matrix) expected_matrix = matrix2 @ matrix1 assert np.allclose(matrix, expected_matrix)
def test_get_unitary_matrix_input_QNode_wireorder(): """Test with QNode as input, and nonstandard wire ordering""" dev = qml.device("default.qubit", wires=5) @qml.qnode(dev) def my_quantum_function(): qml.PauliZ(wires=0) qml.CNOT(wires=[0, 1]) qml.PauliY(wires=1) qml.CRZ(0.2, wires=[2, 3]) qml.PauliX(wires=4) return qml.expval(qml.PauliZ(1)) get_matrix = get_unitary_matrix(my_quantum_function, wire_order=[1, 0, 4, 2, 3]) matrix = get_matrix() CNOT10 = np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]]) expected_matrix = ( reduce(np.kron, [I, I, X, I, I]) @ reduce(np.kron, [I, I, I, qml.CRZ(0.2, wires=[2, 3]).matrix]) @ reduce(np.kron, [Y, I, I, I, I]) @ reduce(np.kron, [CNOT10, I, I, I]) @ reduce(np.kron, [I, Z, I, I, I]) ) assert np.allclose(matrix, expected_matrix)
def test_get_unitary_matrix_invalid_argument(): """Assert error raised when input is neither a tape, QNode, nor quantum function""" get_matrix = get_unitary_matrix(qml.PauliZ(0)) with pytest.raises(ValueError, match="Input is not a tape, QNode, or quantum function"): matrix = get_matrix()
def test_get_unitary_matrix_Toffoli(): """Check the Toffoli matrix by its action on states""" wires = [0, "a", 2, "c", 4] def testcircuit(): qml.Toffoli(wires=[0, 4, "a"]) # test applying to state state0 = [1, 0] state1 = [0, 1] teststate1 = reduce(np.kron, [state1, state1, state1, state1, state1]) teststate2 = reduce(np.kron, [state0, state0, state1, state1, state0]) expected_state1 = reduce(np.kron, [state1, state0, state1, state1, state1]) expected_state2 = teststate2 get_matrix = get_unitary_matrix(testcircuit, wires) matrix = get_matrix() obtained_state1 = matrix @ teststate1 obtained_state2 = matrix @ teststate2 assert np.allclose(obtained_state1, expected_state1) assert np.allclose(obtained_state2, expected_state2)
def test_get_unitary_matrix_input_QNode(): """Test with QNode as input""" dev = qml.device("default.qubit", wires=5) @qml.qnode(dev) def my_quantum_function(): qml.PauliZ(wires=0) qml.CNOT(wires=[0, 1]) qml.PauliY(wires=1) qml.CRZ(0.2, wires=[2, 3]) qml.PauliX(wires=4) return qml.expval(qml.PauliZ(1)) get_matrix = get_unitary_matrix(my_quantum_function) # default wire_order = [0, 1, 2, 3, 4] matrix = get_matrix() expected_matrix = ( reduce(np.kron, [I, I, I, I, X]) @ reduce(np.kron, [I, I, qml.CRZ(0.2, wires=[2, 3]).matrix, I]) @ reduce(np.kron, [I, Y, I, I, I]) @ reduce(np.kron, [CNOT, I, I, I]) @ reduce(np.kron, [Z, I, I, I, I]) ) assert np.allclose(matrix, expected_matrix)
def test_get_unitary_matrix_CRX(): """Test controlled rotation with non-adjacent control and target wires""" testangle = np.pi / 4 wires = [0, 1, 2] def testcircuit(): qml.CRX(testangle, wires=[2, 0]) # test applying to state state0 = [1, 0] state1 = [0, 1] # perform controlled rotation teststate1 = reduce(np.kron, [state1, state1, state1]) # do not perform controlled rotation teststate0 = reduce(np.kron, [state1, state1, state0]) expected_state1 = reduce(np.kron, [qml.RX(testangle, wires=1).matrix @ state1, state1, state1]) expected_state0 = teststate0 get_matrix = get_unitary_matrix(testcircuit, wires) matrix = get_matrix() obtained_state1 = matrix @ teststate1 obtained_state0 = matrix @ teststate0 assert np.allclose(obtained_state1, expected_state1) assert np.allclose(obtained_state0, expected_state0)
def test_get_unitary_matrix_wrong_function(): """Assert error raised when input function is not a quantum function""" def testfunction(x): return x get_matrix = get_unitary_matrix(testfunction, [0]) with pytest.raises(ValueError, match="Function contains no quantum operation"): matrix = get_matrix(1)
def test_single_qubit_full_fusion(self): """Test that a sequence of single-qubit gates all fuse.""" def qfunc(): qml.RZ(0.3, wires=0) qml.Hadamard(wires=0) qml.Rot(0.1, 0.2, 0.3, wires=0) qml.RX(0.1, wires=0) qml.SX(wires=0) qml.T(wires=0) qml.PauliX(wires=0) transformed_qfunc = single_qubit_fusion()(qfunc) # Compare matrices compute_matrix = get_unitary_matrix(qfunc, [0]) matrix_expected = compute_matrix() compute_transformed_matrix = get_unitary_matrix(transformed_qfunc, [0]) matrix_obtained = compute_transformed_matrix() assert check_matrix_equivalence(matrix_expected, matrix_obtained)
def test_single_qubit_fusion_exclude_gates(self): """Test that fusion is correctly skipped for gates explicitly on an exclusion list.""" def qfunc(): # Excluded gate at the start qml.RZ(0.1, wires=0) qml.Hadamard(wires=0) qml.PauliX(wires=0) qml.RZ(0.1, wires=1) qml.CNOT(wires=[0, 1]) qml.Hadamard(wires=0) # Excluded gate after another gate qml.RZ(0.1, wires=0) qml.PauliX(wires=1) qml.PauliZ(wires=1) # Excluded gate after multiple others qml.RZ(0.2, wires=1) original_ops = qml.transforms.make_tape(qfunc)().operations transformed_qfunc = single_qubit_fusion(exclude_gates=["RZ"])(qfunc) transformed_ops = qml.transforms.make_tape( transformed_qfunc)().operations names_expected = [ "RZ", "Rot", "RZ", "CNOT", "Hadamard", "RZ", "Rot", "RZ" ] wires_expected = ([Wires(0)] * 2 + [Wires(1)] + [Wires([0, 1])] + [Wires(0)] * 2 + [Wires(1)] * 2) compare_operation_lists(transformed_ops, names_expected, wires_expected) # Compare matrices compute_matrix = get_unitary_matrix(qfunc, [0, 1]) matrix_expected = compute_matrix() compute_transformed_matrix = get_unitary_matrix( transformed_qfunc, [0, 1]) matrix_obtained = compute_transformed_matrix() assert check_matrix_equivalence(matrix_expected, matrix_obtained)
def test_yzy_to_zyz(self, angles): """Test that a set of rotations of the form YZY is correctly converted to a sequence of the form ZYZ.""" def original_ops(): qml.RY(angles[0], wires=0), qml.RZ(angles[1], wires=0), qml.RY(angles[2], wires=0), compute_matrix = get_unitary_matrix(original_ops, [0]) product_yzy = compute_matrix() z1, y, z2 = _yzy_to_zyz(angles) def transformed_ops(): qml.RZ(z1, wires=0) qml.RY(y, wires=0) qml.RZ(z2, wires=0) compute_transformed_matrix = get_unitary_matrix(transformed_ops, [0]) product_zyz = compute_transformed_matrix() assert check_matrix_equivalence(product_yzy, product_zyz)
def test_full_rot_fusion_autograd(self, angles_1, angles_2): """Test that the fusion of two Rot gates has the same effect as applying the Rots sequentially.""" def original_ops(): qml.Rot(*angles_1, wires=0) qml.Rot(*angles_2, wires=0) compute_matrix = get_unitary_matrix(original_ops, [0]) matrix_expected = compute_matrix() fused_angles = fuse_rot_angles(angles_1, angles_2) matrix_obtained = qml.Rot(*fused_angles, wires=0).matrix assert check_matrix_equivalence(matrix_expected, matrix_obtained)
def test_get_unitary_matrix_default_wireorder(): """Test without specified wire order""" def testcircuit(): qml.PauliX(wires=0) qml.PauliY(wires=1) qml.PauliZ(wires=2) get_matrix = get_unitary_matrix(testcircuit) matrix = get_matrix() expected_matrix = np.kron(X, np.kron(Y, Z)) assert np.allclose(matrix, expected_matrix)
def test_push_mixed_with_matrix(self, direction): """Test that arbitrary gates after controlled gates on controls *and* targets get properly pushed.""" def qfunc(): qml.PauliX(wires=1) qml.S(wires=0) qml.CZ(wires=[0, 1]) qml.CNOT(wires=[1, 0]) qml.PauliY(wires=1) qml.CRY(0.5, wires=[1, 0]) qml.PhaseShift(0.2, wires=0) qml.PauliY(wires=1) qml.T(wires=0) qml.CRZ(-0.3, wires=[0, 1]) qml.RZ(0.2, wires=0) qml.PauliZ(wires=0) qml.PauliX(wires=1) qml.CRY(0.2, wires=[1, 0]) transformed_qfunc = commute_controlled()(qfunc) original_ops = qml.transforms.make_tape(qfunc)().operations transformed_ops = qml.transforms.make_tape( transformed_qfunc)().operations assert len(original_ops) == len(transformed_ops) # Compare matrices compute_matrix = get_unitary_matrix(qfunc, [0, 1]) matrix_expected = compute_matrix() compute_transformed_matrix = get_unitary_matrix( transformed_qfunc, [0, 1]) matrix_obtained = compute_transformed_matrix() assert check_matrix_equivalence(matrix_expected, matrix_obtained)
def test_get_unitary_matrix_wronglabel(): """Assert error raised when wire labels in wire_order and circuit are inconsistent""" def circuit(): qml.PauliX(wires=1) qml.PauliZ(wires=0) wires = [0, "b"] get_matrix = get_unitary_matrix(circuit, wires) with pytest.raises( ValueError, match="Wires in circuit are inconsistent with those in wire_order" ): matrix = get_matrix()
def test_two_qubit_decomposition_tf(self, U, wires): """Test that a two-qubit operation in Tensorflow is correctly decomposed.""" tf = pytest.importorskip("tensorflow") U = tf.Variable(U, dtype=tf.complex128) obtained_decomposition = two_qubit_decomposition(U, wires=wires) with qml.tape.QuantumTape() as tape: for op in obtained_decomposition: qml.apply(op) obtained_matrix = get_unitary_matrix(tape, wire_order=wires)() assert check_matrix_equivalence(U, obtained_matrix, atol=1e-7)
def test_single_qubit_fusion_multiple_qubits(self): """Test that all sequences of single-qubit gates across multiple qubits fuse properly.""" def qfunc(): qml.RZ(0.3, wires="a") qml.RY(0.5, wires="a") qml.Rot(0.1, 0.2, 0.3, wires="b") qml.RX(0.1, wires="a") qml.CNOT(wires=["b", "a"]) qml.SX(wires="b") qml.S(wires="b") qml.PhaseShift(0.3, wires="b") transformed_qfunc = single_qubit_fusion()(qfunc) original_ops = qml.transforms.make_tape(qfunc)().operations transformed_ops = qml.transforms.make_tape( transformed_qfunc)().operations names_expected = ["Rot", "Rot", "CNOT", "Rot"] wires_expected = [ Wires("a"), Wires("b"), Wires(["b", "a"]), Wires("b") ] compare_operation_lists(transformed_ops, names_expected, wires_expected) # Check matrix representation compute_matrix = get_unitary_matrix(qfunc, ["a", "b"]) matrix_expected = compute_matrix() compute_transformed_matrix = get_unitary_matrix( transformed_qfunc, ["a", "b"]) matrix_obtained = compute_transformed_matrix() assert check_matrix_equivalence(matrix_expected, matrix_obtained)
def test_two_qubit_decomposition_1_cnot(self, U, wires): """Test that a two-qubit matrix using one CNOT is correctly decomposed.""" U = _convert_to_su4(np.array(U)) assert _compute_num_cnots(U) == 1 obtained_decomposition = two_qubit_decomposition(U, wires=wires) assert len(obtained_decomposition) == 5 with qml.tape.QuantumTape() as tape: for op in obtained_decomposition: qml.apply(op) obtained_matrix = get_unitary_matrix(tape, wire_order=wires)() assert check_matrix_equivalence(U, obtained_matrix, atol=1e-7)
def test_get_unitary_matrix_input_tape(): """Test with quantum tape as input""" with qml.tape.QuantumTape() as tape: qml.RX(0.432, wires=0) qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 1]) qml.RX(0.133, wires=1) get_matrix = get_unitary_matrix(tape) matrix = get_matrix() part_expected_matrix = np.kron(qml.RY(0.543, wires=0).matrix @ qml.RX(0.432, wires=0).matrix, I) expected_matrix = np.kron(I, qml.RX(0.133, wires=1).matrix) @ CNOT @ part_expected_matrix assert np.allclose(matrix, expected_matrix)
def test_two_qubit_decomposition_tensor_products(self, U_pair, wires): """Test that a two-qubit tensor product matrix is correctly decomposed.""" U = _convert_to_su4( qml.math.kron(np.array(U_pair[0]), np.array(U_pair[1]))) assert _compute_num_cnots(U) == 0 obtained_decomposition = two_qubit_decomposition(U, wires=wires) assert len(obtained_decomposition) == 2 with qml.tape.QuantumTape() as tape: for op in obtained_decomposition: qml.apply(op) obtained_matrix = get_unitary_matrix(tape, wire_order=wires)() assert check_matrix_equivalence(U, obtained_matrix, atol=1e-7)
def test_get_unitary_matrix_multiple_ops(): """Check the total matrix for a circuit containing multiple gates. Also checks that non-integer wires work""" wires = ["a", "b", "c"] def testcircuit(): qml.PauliX(wires="a") qml.S(wires="b") qml.Hadamard(wires="c") qml.CNOT(wires=["b", "c"]) get_matrix = get_unitary_matrix(testcircuit, wires) matrix = get_matrix() expected_matrix = np.kron(I, CNOT) @ np.kron(X, np.kron(S, H)) assert np.allclose(matrix, expected_matrix)
def test_get_unitary_matrix_nonparam_1qubit_ops(op, wire): """Check the matrices for different nonparametrized single-qubit gates, which are acting on different qubits in a space of three qubits.""" wires = [0, 1, 2] def testcircuit(wire): op(wires=wire) get_matrix = get_unitary_matrix(testcircuit, wires) matrix = get_matrix(wire) if wire == 0: expected_matrix = np.kron(op(wires=wire).matrix, np.eye(4)) if wire == 1: expected_matrix = np.kron(np.eye(2), np.kron(op(wires=wire).matrix, np.eye(2))) if wire == 2: expected_matrix = np.kron(np.eye(4), op(wires=wire).matrix) assert np.allclose(matrix, expected_matrix)
def test_two_qubit_decomposition_tensor_products_torch( self, U_pair, wires): """Test that a two-qubit tensor product in Torch is correctly decomposed.""" torch = pytest.importorskip("torch") U1 = torch.tensor(U_pair[0], dtype=torch.complex128) U2 = torch.tensor(U_pair[1], dtype=torch.complex128) U = qml.math.kron(U1, U2) obtained_decomposition = two_qubit_decomposition(U, wires=wires) with qml.tape.QuantumTape() as tape: for op in obtained_decomposition: qml.apply(op) obtained_matrix = get_unitary_matrix(tape, wire_order=wires)() assert check_matrix_equivalence(U, obtained_matrix, atol=1e-7)
def test_two_qubit_decomposition_3_cnots(self, U, wires): """Test that a two-qubit matrix using 3 CNOTs is correctly decomposed.""" U = _convert_to_su4(np.array(U)) assert _compute_num_cnots(U) == 3 obtained_decomposition = two_qubit_decomposition(U, wires=wires) assert len(obtained_decomposition) == 10 with qml.tape.QuantumTape() as tape: for op in obtained_decomposition: qml.apply(op) obtained_matrix = get_unitary_matrix(tape, wire_order=wires)() # We check with a slightly great tolerance threshold here simply because the # test matrices were copied in here with reduced precision. assert check_matrix_equivalence(U, obtained_matrix, atol=1e-7)
def test_get_unitary_matrix_input_tape_wireorder(): """Test with quantum tape as input, and nonstandard wire ordering""" with qml.tape.QuantumTape() as tape: qml.RX(0.432, wires=0) qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 1]) qml.RX(0.133, wires=1) get_matrix = get_unitary_matrix(tape, wire_order=[1, 0]) matrix = get_matrix() # CNOT where the second wire is the control wire, as opposed to qml.CNOT.matrix CNOT10 = np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]]) part_expected_matrix = np.kron(I, qml.RY(0.543, wires=0).matrix @ qml.RX(0.432, wires=0).matrix) expected_matrix = np.kron(qml.RX(0.133, wires=1).matrix, I) @ CNOT10 @ part_expected_matrix assert np.allclose(matrix, expected_matrix)
def test_two_qubit_decomposition_jax(self, U, wires): """Test that a two-qubit operation in JAX is correctly decomposed.""" jax = pytest.importorskip("jax") 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.complex128) obtained_decomposition = two_qubit_decomposition(U, wires=wires) with qml.tape.QuantumTape() as tape: for op in obtained_decomposition: qml.apply(op) obtained_matrix = get_unitary_matrix(tape, wire_order=wires)() assert check_matrix_equivalence(U, obtained_matrix, atol=1e-7)
def test_get_unitary_matrix_MultiControlledX(): """Test with many control wires""" wires = [0, 1, 2, 3, 4, 5] def testcircuit(): qml.MultiControlledX(control_wires=[0, 2, 4, 5], wires=3) state0 = [1, 0] state1 = [0, 1] teststate1 = reduce(np.kron, [state1, state1, state1, state1, state1, state1]) teststate2 = reduce(np.kron, [state0, state1, state0, state0, state1, state0]) expected_state1 = reduce(np.kron, [state1, state1, state1, state0, state1, state1]) expected_state2 = teststate2 get_matrix = get_unitary_matrix(testcircuit, wires) matrix = get_matrix() obtained_state1 = matrix @ teststate1 obtained_state2 = matrix @ teststate2 assert np.allclose(obtained_state1, expected_state1) assert np.allclose(obtained_state2, expected_state2)
def test_get_unitary_matrix_interface_jax(): """Test with 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) dev = qml.device("default.qubit", wires=3) def circuit(theta): qml.RZ(theta[0], wires=0) qml.RZ(theta[1], wires=1) qml.CRY(theta[2], wires=[1, 2]) return qml.expval(qml.PauliZ(1)) # set qnode interface qnode = qml.QNode(circuit, dev, interface="jax") get_matrix = get_unitary_matrix(qnode) # input jax parameters theta = jnp.array([0.1, 0.2, 0.3], dtype=jnp.float64) matrix = get_matrix(theta) # expected matrix matrix1 = np.kron( qml.RZ(theta[0], wires=0).matrix, np.kron(qml.RZ(theta[1], wires=1).matrix, I) ) matrix2 = np.kron(I, qml.CRY(theta[2], wires=[1, 2]).matrix) expected_matrix = matrix2 @ matrix1 assert np.allclose(matrix, expected_matrix)