def test_row_sum_sub_algorithm_g_test():
    """
    Test the behavior of the subroutine of the rowsum routine

    g(x1, z1, x2, z2) is a function that returns the exponent that $i$ is raised (0, -1, 1)
    when pauli matrices represented by x1z1 and x2z2 are multiplied together.

    Logic table:
        I1 = {x1 = 0, z1 = 0}
        I2 = {x2 = 0, z2 = 0}
        g(I1, I2) = 0

        I1 = {x1 = 0, z1 = 0}
        X2 = {x2 = 1, z2 = 0}
        g(I1, X2) = 0

        I1 = {x1 = 0, z1 = 0}
        Y2 = {x2 = 1, z2 = 1}
        g(I1, X2) = 0

        I1 = {x1 = 0, z1 = 0}
        Z2 = {x2 = 0, z2 = 1}
        g(I1, X2) = 0

        ---------------------

        X1 = {x1 = 1, z1 = 0}
        I2 = {x2 = 0, z2 = 0}
        g(I1, I2) = 0

        X1 = {x1 = 1, z1 = 0}
        X2 = {x2 = 1, z2 = 0}
        g(I1, X2) = 0

        X1 = {x1 = 1, z1 = 0}
        Y2 = {x2 = 1, z2 = 1}
        g(I1, X2) = 1

        X1 = {x1 = 1, z1 = 0}
        Z2 = {x2 = 0, z2 = 1}
        g(I1, X2) = -1

        ---------------------
        etc...
    """
    num_qubits = 4
    qvmstab = QVM_Stabilizer(num_qubits=num_qubits)

    # loop over the one-qubit pauli group and make sure the power of $i$ is correct
    # this test would be better if we stored levi-civta
    pauli_map = {'X': (1, 0), 'Y': (1, 1), 'Z': (0, 1), 'I': (0, 0)}
    for pi, pj in product(PAULI_OPS, repeat=2):
        i_coeff = qvmstab._g_update(pauli_map[pi][0], pauli_map[pi][1],
                                    pauli_map[pj][0], pauli_map[pj][1])
        # print(pi, pj, "".join([x for x in [pi, pj]]), PAULI_COEFF["".join([x for x in [pi, pj]])], 1j**i_coeff)
        assert np.isclose(PAULI_COEFF["".join([x for x in [pi, pj]])],
                          1j**i_coeff)
def test_measurement_noncommuting():
    """
    Test measurements of stabilizer state from tableau

    This tests the non-commuting measurement operator
    """
    # generate bell state then measure each qubit sequentially
    prog = Program().inst(H(0)).measure(0, 0)
    qvmstab = QVM_Stabilizer()
    results = qvmstab.run(prog, trials=5000)
    assert np.isclose(np.mean(results), 0.5, rtol=0.1)
def test_measurement_commuting():
    """
    Test measuremt of stabilzier state from tableau

    This tests when the measurement operator commutes with the stabilizers

    To test we will first test draw's from a blank stabilizer and then with X
    """
    identity_program = Program().inst([I(0)]).measure(0, 0)
    qvmstab = QVM_Stabilizer()
    results = qvmstab.run(identity_program, trials=1000)
    assert all(np.array(results) == 0)
def test_measurement_commuting_result_one():
    """
    Test measuremt of stabilzier state from tableau

    This tests when the measurement operator commutes with the stabilizers

    This time we will generate the stabilizer -Z|1> = |1> so we we need to do
    a bitflip...not just identity.  A Bitflip is HSSH = X
    """
    identity_program = Program().inst([H(0), S(0), S(0), H(0)]).measure(0, 0)
    qvmstab = QVM_Stabilizer()
    results = qvmstab.run(identity_program, trials=1000)
    assert all(np.array(results) == 1)
Esempio n. 5
0
def QVMConnection(type_trans='wavefunction',
                  gate_set=gate_matrix, noise_model=None):
        """
        Initialize a qvm of a particular type. The type corresponds to the
        type of transition the QVM can perform.

        Currently available transitions:
            wavefunction
            unitary
            (density) support to be implemented
    
        'Wavefunction' is set by default. No noise/t1/t2 params are supported
        in this ref-qvm yet. The QVM uses the gate_set in the
        gate_matrix dictionary by default

        :param type_trans: Transition type of the qvm. Either wavefunction or unitary
        :param gate_set: The set of gates that each qubit or pair of qubits can perform
        """

        if type_trans == 'wavefunction':
            qvm = QVM_Wavefunction(gate_set=gate_set)

        elif type_trans == 'unitary':
            qvm = QVM_Unitary(gate_set=gate_set)

        elif type_trans == 'density':
            qvm = QVM_Density(gate_set=gate_set, noise_model=noise_model)

        elif type_trans == 'stabilizer':
            qvm = QVM_Stabilizer(gate_set=stabilizer_gate_matrix)
        else:
            raise TypeError("{} is not a valid QVM type.".format(type_trans))

        return qvm
def test_simulation_phase():
    """
    Test if the Phase gate is applied correctly to the tableau

    S|0> = |0>

    S|+> = |R>
    """
    prog = Program().inst(S(0))
    qvmstab = QVM_Stabilizer(num_qubits=1)
    qvmstab._apply_phase(prog.instructions[0])
    true_stab = np.array([[1, 1, 0], [0, 1, 0]])
    assert np.allclose(true_stab, qvmstab.tableau)

    prog = Program().inst([H(0), S(0)])
    qvmstab = QVM_Stabilizer(num_qubits=1)
    qvmstab._apply_hadamard(prog.instructions[0])
    qvmstab._apply_phase(prog.instructions[1])
    true_stab = np.array([[0, 1, 0], [1, 1, 0]])
    assert np.allclose(true_stab, qvmstab.tableau)
def test_initialization():
    """
    Test if upon initialization the correct size tableau is set up
    """
    num_qubits = 4
    qvmstab = QVM_Stabilizer(num_qubits=num_qubits)
    assert qvmstab.num_qubits == num_qubits
    assert qvmstab.tableau.shape == (2 * num_qubits, 2 * num_qubits + 1)

    initial_tableau = np.hstack(
        (np.eye(2 * num_qubits), np.zeros((2 * num_qubits, 1))))
    assert np.allclose(initial_tableau, qvmstab.tableau)

    num_qubits = 6
    qvmstab = QVM_Stabilizer(num_qubits=num_qubits)
    assert qvmstab.num_qubits == num_qubits
    assert qvmstab.tableau.shape == (2 * num_qubits, 2 * num_qubits + 1)

    initial_tableau = np.hstack(
        (np.eye(2 * num_qubits), np.zeros((2 * num_qubits, 1))))
    assert np.allclose(initial_tableau, qvmstab.tableau)
def test_random_stabilizer_circuit():
    """
    Generate a random stabilizer circuit from {CNOT, H, S, MEASURE}

    Compare the outcome to full state evolution
    """
    num_qubits = 2
    # for each qubit pick a set of operations
    gate_operations = {1: H, 2: S, 3: CNOT}
    np.random.seed(42)
    num_gates = 1000  # program has 100 gates in it

    prog = Program()
    for _ in range(num_gates):
        for jj in range(num_qubits):
            instruction_idx = np.random.randint(1, 4)
            if instruction_idx == 3:
                gate = gate_operations[instruction_idx](jj,
                                                        (jj + 1) % num_qubits)
            else:
                gate = gate_operations[instruction_idx](jj)
            prog.inst(gate)

    qvmstab = QVM_Stabilizer()
    wf = qvmstab.wavefunction(prog)
    wf = wf.amplitudes.reshape((-1, 1))
    rho_trial = wf.dot(np.conj(wf.T))

    qvm = QVMConnection(type_trans='wavefunction')
    rho, _ = qvm.wavefunction(prog)
    rho = rho.amplitudes.reshape((-1, 1))
    rho_true = rho.dot(np.conj(rho.T))
    assert np.allclose(rho_true, rho_trial)

    rho_from_stab = qvmstab.density(prog)
    assert np.allclose(rho_from_stab, rho_true)
def test_rowsum_full():
    """
    Test the rowsum subroutine.

    This routine replaces row h with P(i) * P(h) where P is the pauli operator represented by the row
    given by the index.  It uses the summation to accumulate whether the phase is +1 or -1 and then
    xors together the elements of the rows replacing row h
    """
    # now try generating random valid elements from 2-qubit group
    for _ in range(100):
        num_qubits = 6
        pauli_terms = []
        for _ in range(num_qubits):
            pauli_terms.append(
                reduce(lambda x, y: x * y, [
                    pauli_subgroup[x](idx) for idx, x in enumerate(
                        np.random.randint(1, 4, num_qubits))
                ]))
        try:
            stab_mat = pauli_stabilizer_to_binary_stabilizer(pauli_terms)
        except:
            # we have to do this because I'm not strictly making valid n-qubit
            # stabilizers
            continue
        qvmstab = QVM_Stabilizer(num_qubits=num_qubits)
        qvmstab.tableau[num_qubits:, :] = stab_mat
        p_on_i = qvmstab._rowsum_phase_accumulator(num_qubits, num_qubits + 1)
        if p_on_i not in [1, 3]:
            coeff_test = pauli_terms[1] * pauli_terms[0]
            assert np.isclose(coeff_test.coefficient, (1j)**p_on_i)
            qvmstab._rowsum(num_qubits, num_qubits + 1)
            try:
                pauli_op = binary_stabilizer_to_pauli_stabilizer(
                    qvmstab.tableau[[num_qubits], :])[0]
            except:
                continue
            true_pauli_op = pauli_terms[1] * pauli_terms[0]
            assert pauli_op == true_pauli_op
def test_bell_state_measurements():
    prog = Program().inst(H(0), CNOT(0, 1)).measure(0, 0).measure(1, 1)
    qvmstab = QVM_Stabilizer()
    results = qvmstab.run(prog, trials=5000)
    assert np.isclose(np.mean(results), 0.5, rtol=0.1)
    assert all([x[0] == x[1] for x in results])
def test_simulation_cnot():
    """
    Test if the simulation of CNOT is accurate
    :return:
    """
    prog = Program().inst([H(0), CNOT(0, 1)])
    qvmstab = QVM_Stabilizer(num_qubits=2)
    qvmstab._apply_hadamard(prog.instructions[0])
    qvmstab._apply_cnot(prog.instructions[1])

    # assert that ZZ XX stabilizes a bell state
    true_stabilizers = [sX(0) * sX(1), sZ(0) * sZ(1)]
    test_paulis = binary_stabilizer_to_pauli_stabilizer(qvmstab.tableau[2:, :])
    for idx, term in enumerate(test_paulis):
        assert term == true_stabilizers[idx]

    # test that CNOT does nothing to |00> state
    prog = Program().inst([CNOT(0, 1)])
    qvmstab = QVM_Stabilizer(num_qubits=2)
    qvmstab._apply_cnot(prog.instructions[0])
    true_tableau = np.array([
        [1, 1, 0, 0, 0],  # X1 -> X1 X2
        [0, 1, 0, 0, 0],  # X2 -> X2
        [0, 0, 1, 0, 0],  # Z1 -> Z1
        [0, 0, 1, 1, 0]
    ])  # Z2 -> Z1 Z2

    # note that Z1, Z1 Z2 still stabilizees |00>
    assert np.allclose(true_tableau, qvmstab.tableau)

    # test that CNOT produces 11 state after X
    prog = Program().inst([H(0), S(0), S(0), H(0), CNOT(0, 1)])
    qvmstab = QVM_Stabilizer(num_qubits=2)
    qvmstab._apply_hadamard(prog.instructions[0])
    qvmstab._apply_phase(prog.instructions[1])
    qvmstab._apply_phase(prog.instructions[2])
    qvmstab._apply_hadamard(prog.instructions[3])
    qvmstab._apply_cnot(prog.instructions[4])
    true_tableau = np.array([[1, 1, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 1],
                             [0, 0, 1, 1, 0]])

    # note that -Z1, Z1 Z2 still stabilizees |11>
    assert np.allclose(true_tableau, qvmstab.tableau)

    test_paulis = binary_stabilizer_to_pauli_stabilizer(
        qvmstab.stabilizer_tableau())
    state = project_stabilized_state(test_paulis,
                                     qvmstab.num_qubits,
                                     classical_state=[1, 1])
    state_2 = project_stabilized_state(test_paulis, qvmstab.num_qubits)

    assert np.allclose(np.array(state.todense()), np.array(state_2.todense()))
def test_simulation_hadamard():
    """
    Test if Hadamard is applied correctly to the tableau

    The first example will be a 1 qubit example. where we perform H | 0 > = |0> + |1>.
    Therefore we expect the tableau to represent the stablizer X
    """
    prog = Program().inst(H(0))
    qvmstab = QVM_Stabilizer(num_qubits=1)
    qvmstab._apply_hadamard(prog.instructions[0])
    x_stabilizer = np.array([[0, 1, 0], [1, 0, 0]])
    assert np.allclose(x_stabilizer, qvmstab.tableau)

    qvmstab = QVM_Stabilizer(num_qubits=2)
    qvmstab._apply_hadamard(prog.instructions[0])
    x_stabilizer = np.array([[0, 0, 1, 0, 0], [0, 1, 0, 0, 0], [1, 0, 0, 0, 0],
                             [0, 0, 0, 1, 0]])
    assert np.allclose(x_stabilizer, qvmstab.tableau)

    prog = Program().inst(H(1))
    qvmstab = QVM_Stabilizer(num_qubits=2)
    qvmstab._apply_hadamard(prog.instructions[0])
    x_stabilizer = np.array([[1, 0, 0, 0, 0], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0],
                             [0, 1, 0, 0, 0]])
    assert np.allclose(x_stabilizer, qvmstab.tableau)

    prog = Program().inst([H(0), H(1)])
    qvmstab = QVM_Stabilizer(num_qubits=2)
    qvmstab._apply_hadamard(prog.instructions[0])
    qvmstab._apply_hadamard(prog.instructions[1])
    x_stabilizer = np.array([[0, 0, 1, 0, 0], [0, 0, 0, 1, 0], [1, 0, 0, 0, 0],
                             [0, 1, 0, 0, 0]])
    assert np.allclose(x_stabilizer, qvmstab.tableau)

    prog = Program().inst([H(0), H(1), H(0), H(1)])
    qvmstab = QVM_Stabilizer(num_qubits=2)
    qvmstab._apply_hadamard(prog.instructions[0])
    qvmstab._apply_hadamard(prog.instructions[1])
    qvmstab._apply_hadamard(prog.instructions[2])
    qvmstab._apply_hadamard(prog.instructions[3])
    x_stabilizer = np.array([[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0],
                             [0, 0, 0, 1, 0]])
    assert np.allclose(x_stabilizer, qvmstab.tableau)
def test_rowsum_phase_accumulator():
    """
    Test the accuracy of the phase accumulator.  This subroutine keeps track of the power that $i$ is raised to
    when multiplying two PauliTerms together.  PauliTerms are now composed of multi single-qubit Pauli's.

    The formula is phase_accumulator(h, i) = 2 * rh + 2 * ri + \sum_{j}^{n}g(x_{ij}, z_{ij}, x_{hj}, z_{hj}

    The returned value is presented mod 4.

    notice that since r_h indicates +1 or -1 this corresponds to i^{0} or i^{2}.  Given r_{h/i} takes on {0, 1} then
    we need to multiply by 2 get the correct power of $i$ for the Pauli Term to account for multiplication.

    In order to test we will generate random elements of P_{n} and then multiply them together keeping track of the
    $i$ power.  We will then compare this to the phase_accumulator subroutine.

    We have to load in the stabilizer into an empty qvmstab object because the subroutine needs a tableau as a reference
    """
    num_qubits = 2
    pauli_terms = [sX(0) * sX(1), sZ(0) * sZ(1)]
    stab_mat = pauli_stabilizer_to_binary_stabilizer(pauli_terms)
    qvmstab = QVM_Stabilizer(num_qubits=num_qubits)
    qvmstab.tableau[num_qubits:, :] = stab_mat
    exp_on_i = qvmstab._rowsum_phase_accumulator(2, 3)
    assert exp_on_i == 2

    num_qubits = 2
    pauli_terms = [sZ(0) * sI(1), sZ(0) * sZ(1)]
    stab_mat = pauli_stabilizer_to_binary_stabilizer(pauli_terms)
    qvmstab = QVM_Stabilizer(num_qubits=num_qubits)
    qvmstab.tableau[num_qubits:, :] = stab_mat
    exp_on_i = qvmstab._rowsum_phase_accumulator(2, 3)
    assert exp_on_i == 0

    # now try generating random valid elements from 2-qubit group
    for _ in range(100):
        num_qubits = 6
        pauli_terms = []
        for _ in range(num_qubits):
            pauli_terms.append(
                reduce(lambda x, y: x * y, [
                    pauli_subgroup[x](idx) for idx, x in enumerate(
                        np.random.randint(1, 4, num_qubits))
                ]))
        stab_mat = pauli_stabilizer_to_binary_stabilizer(pauli_terms)
        qvmstab = QVM_Stabilizer(num_qubits=num_qubits)
        qvmstab.tableau[num_qubits:, :] = stab_mat
        p_on_i = qvmstab._rowsum_phase_accumulator(num_qubits, num_qubits + 1)
        coeff_test = pauli_terms[1] * pauli_terms[0]
        assert np.isclose(coeff_test.coefficient, (1j)**p_on_i)