Esempio n. 1
0
    def encode_zero(self, prog: Program, block: CodeBlock, ancilla: CodeBlock, scratch: MemoryChunk):
        if len(scratch) < self.error_correct_scratch_size:
            raise ValueError("scratch buffer is too small")

        flag = scratch[0]
        outcome = scratch[1]
        scratch = scratch[2:]

        # To do a somewhat fault tolerant preparation, we will do a noisy preparation of the target
        # block, then perform a Steane error correction using a noisy ancilla. Instead of actually
        # doing an error correction though, we will just do error detection and repeat until no
        # errors are detected. If no errors are detected, then either both the block and ancilla
        # were clean or they both has the exact same errors, which is unlikely. This idea comes from
        # section 4.6 of "An Introduction to Quantum Error Correction and Fault-Tolerant Quantum
        # Computation" by Daniel Gottesman.
        loop_prog = Program()
        loop_prog += gates.MOVE(flag, 0)

        block.reset(loop_prog)
        loop_prog += self.noisy_encode_zero(block.qubits)

        self._error_detect_x(loop_prog, block, ancilla, outcome, scratch, include_operators=True)
        loop_prog += gates.IOR(flag, outcome)

        self._error_detect_z(loop_prog, block, ancilla, outcome, scratch, include_operators=False)
        loop_prog += gates.IOR(flag, outcome)

        prog += gates.MOVE(flag, 1)
        prog.while_do(flag, loop_prog)
Esempio n. 2
0
 def reset(self, prog: Program):
     """
     Reset the physical qubits to the |0>^{\otimes n} state and the errors to 0.
     This code block must not be entangled with any other qubits in the system.
     """
     prog += (gates.MEASURE(self.qubits[i], self.x_errors[i])
              for i in range(self.n))
     for i in range(self.n):
         prog.if_then(self.x_errors[i], gates.X(self.qubits[i]))
         prog += gates.MOVE(self.x_errors[i], 0)
         prog += gates.MOVE(self.z_errors[i], 0)
Esempio n. 3
0
def majority_vote(prog: Program, inputs: MemoryChunk, output: MemoryReference,
                  scratch_int: MemoryChunk):
    if len(scratch_int) < 2:
        raise ValueError("scratch_int buffer too small")
    if len(inputs) % 2 == 0:
        raise ValueError("inputs length must be odd")

    prog += gates.MOVE(scratch_int[0], 0)
    for bit in inputs:
        prog += gates.CONVERT(scratch_int[1], bit)
        prog += gates.ADD(scratch_int[0], scratch_int[1])

    threshold = (len(inputs) + 1) // 2
    prog += gates.MOVE(scratch_int[1], threshold)
    prog += gates.GE(output, scratch_int[0], scratch_int[1])
Esempio n. 4
0
def quil_classical_detect(prog: Program, codeword: MemoryChunk, errors: MemoryChunk,
                          outcome: MemoryReference, scratch: MemoryChunk, parity_check):
    """
    Extend a Quil program with instructions to detect errors in a noisy classical codeword. Sets the
    outcome bit if any errors are detected and unsets it otherwise.
    """
    m, n = parity_check.shape
    if len(codeword) != n:
        raise ValueError("codeword is of incorrect size")
    if len(errors) != n:
        raise ValueError("errors is of incorrect size")
    if len(scratch) < m + 2:
        raise ValueError("scratch buffer is too small")

    # Add in existing known errors to the noisy codeword.
    prog += (gates.XOR(codeword[i], errors[i]) for i in range(n))

    # Compute the syndrome by multiplying by the parity check matrix.
    syndrome = scratch[2:m+2]
    quil_classical.matmul(prog, parity_check, codeword, syndrome, scratch[:2])

    # Revert codeword back to the state without error corrections.
    prog += (gates.XOR(codeword[i], errors[i]) for i in range(n))

    # Set outcome only if syndrome is non-zero.
    prog += gates.MOVE(outcome, 0)
    prog += (gates.IOR(outcome, syndrome[i]) for i in range(m))
Esempio n. 5
0
    def test_string_match(self):
        test_cases = [
            ([0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], True),
            ([0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 0, 1], True),
            ([0, 0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 0, 1, 1], True),
            ([0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], False),
            ([0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 1], False),
            ([0, 0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 0, 0, 1], False),
        ]

        for vec1, vec2, expected_match in test_cases:
            n = len(vec1)
            prog = Program()
            raw_mem = prog.declare('ro', 'BIT', n + 2)
            self.initialize_memory(prog, raw_mem)

            mem = MemoryChunk(raw_mem, 0, n + 2)
            vec = mem[0:n]
            output = mem[n:(n + 1)]
            scratch = mem[(n + 1):(n + 2)]

            # Copy data from vec2 into vec.
            for i in range(n):
                prog += gates.MOVE(vec[i], vec2[i])

            match_vec = np.array(vec1, dtype='int')
            quil_classical.string_match(prog, vec, match_vec, output, scratch)

            results = self.qvm.run(prog)[0]
            self.assertEqual(results[n] == 1, expected_match)
Esempio n. 6
0
    def test_matmul(self):
        m, n = 20, 10
        mat = np.random.randint(0, 2, size=(m, n), dtype='int')
        vec = np.random.randint(0, 2, size=n, dtype='int')

        prog = Program()
        raw_mem = prog.declare('ro', 'BIT', n + m + 1)
        self.initialize_memory(prog, raw_mem)

        mem = MemoryChunk(raw_mem, 0, n + m + 1)
        vec_in = mem[0:n]
        vec_out = mem[n:(n + m)]
        scratch = mem[(n + m):(n + m + 1)]

        # Copy data from vec into program memory.
        for i in range(n):
            prog += gates.MOVE(vec_in[i], int(vec[i]))

        quil_classical.matmul(prog, mat, vec_in, vec_out, scratch)

        results = self.qvm.run(prog)[0]

        actual = results[n:(n + m)]
        expected = np.mod(np.matmul(mat, vec), 2)
        for i in range(m):
            self.assertEqual(actual[i], int(expected[i]))
Esempio n. 7
0
def string_match(prog: Program, mem: MemoryChunk, vec, output: MemoryChunk,
                 scratch: MemoryChunk):
    """
    Compares a bit string in Quil classical memory to a constant vector. If they are equal, the
    function assigns output to 1, otherwise 0.
    """
    n = len(mem)
    if vec.size != n:
        raise ValueError("length of mem and vec do not match")
    if len(scratch) < 1:
        raise ValueError("scratch buffer is too small")

    prog += gates.MOVE(output[0], 0)
    for i in range(n):
        prog += gates.MOVE(scratch[0], mem[i])
        prog += gates.XOR(scratch[0], int(vec[i]))
        prog += gates.IOR(output[0], scratch[0])
    prog += gates.NOT(output[0])
Esempio n. 8
0
def _initialize_memory(prog: Program, mem: MemoryReference,
                       qubits: List[QubitPlaceholder]):
    """
    The QVM has some weird behavior where classical memory registers have to be initialized with
    a MEASURE before any values can be MOVE'd to them. So for memory regions used internally,
    initialize memory by performing superfluous measurements.
    """
    prog += (gates.MEASURE(qubits[i % len(qubits)], mem[i])
             for i in range(mem.declared_size))
    prog += (gates.MOVE(mem[i], 0) for i in range(mem.declared_size))
Esempio n. 9
0
def matmul(prog: Program, mat, vec: MemoryChunk, result: MemoryChunk,
           scratch: MemoryChunk):
    """
    Extend a Quil program with instructions to perform a classical matrix multiplication of a fixed
    binary matrix with a vector of bits stored in classical memory.
    """
    m, n = mat.shape
    if len(vec) != n:
        raise ValueError("mat and vec are of incompatible sizes")
    if len(result) != m:
        raise ValueError("mat and result are of incompatible sizes")
    if len(scratch) < 1:
        raise ValueError("scratch buffer is too small")

    for i in range(m):
        prog += gates.MOVE(result[i], 0)
        for j in range(n):
            prog += gates.MOVE(scratch[0], vec[j])
            prog += gates.AND(scratch[0], int(mat[i][j]))
            prog += gates.XOR(result[i], scratch[0])
Esempio n. 10
0
def conditional_xor(prog: Program, mem: MemoryChunk, vec, flag: MemoryChunk,
                    scratch: MemoryChunk):
    """
    Conditionally bitwise XORs a constant vector to a bit string in Quil classical memory if a
    flag bit is set. If the flag bit is not set, this does not modify the memory.
    """
    n = len(mem)
    if vec.size != n:
        raise ValueError("length of mem and vec do not match")

    for i in range(n):
        prog += gates.MOVE(scratch[0], flag[0])
        prog += gates.AND(scratch[0], int(vec[i]))
        prog += gates.XOR(mem[i], scratch[0])
Esempio n. 11
0
def qsolution(ansatz, opt_angles):
    """Returns the wavefunction of the ansatz at the optimal angles."""
    prog = Program()
    memory_map = {"theta": opt_angles}
    for name, arr in memory_map.items():
        for index, value in enumerate(arr):
            prog += gates.MOVE(gates.MemoryReference(name, offset=index),
                               value)

    ansatz = prog + ansatz
    soln = pyquil.quil.percolate_declares(ansatz)

    wfsim = pyquil.api.WavefunctionSimulator()
    return wfsim.wavefunction(soln).amplitudes
Esempio n. 12
0
    def encode_plus(self, prog: Program, block: CodeBlock, ancilla: CodeBlock, scratch: MemoryChunk):
        if len(scratch) < self.error_correct_scratch_size:
            raise ValueError("scratch buffer is too small")

        flag = scratch[0]
        outcome = scratch[1]
        scratch = scratch[2:]

        # See encode_zero for more thorough comments.
        loop_prog = Program()
        loop_prog += gates.MOVE(flag, 0)

        block.reset(loop_prog)
        loop_prog += self.noisy_encode_plus(block.qubits)

        self._error_detect_x(loop_prog, block, ancilla, outcome, scratch, include_operators=False)
        loop_prog += gates.IOR(flag, outcome)

        self._error_detect_z(loop_prog, block, ancilla, outcome, scratch, include_operators=True)
        loop_prog += gates.IOR(flag, outcome)

        prog += gates.MOVE(flag, 1)
        prog.while_do(flag, loop_prog)
Esempio n. 13
0
    def measure(self, prog: Program, data: CodeBlock, index: int, outcome: MemoryReference,
                ancilla_1: CodeBlock, ancilla_2: CodeBlock,
                scratch: MemoryChunk, scratch_int: MemoryChunk):
        """
        Extend a Quil program to measure the logical qubit in the Z basis. Ancilla must be in a
        logical |0> state.

        Index is the index of the logical qubit within the code block. Currently must be 0.

        This measurement is made fault tolerant by repeating a noisy measurement 2t + 1 times and
        returning a majority vote.

        This yields control after each fault tolerant operation so that a round of error correction
        may be performed globally if required.
        """
        if index != 0:
            raise ValueError("only one logical qubit per code block")
        if data.n != self.n:
            raise ValueError("data code word is of incorrect size")
        if ancilla_1.n != self.n:
            raise ValueError("ancilla_1 code word is of incorrect size")
        if ancilla_2.n != self.n:
            raise ValueError("ancilla_2 code word is of incorrect size")
        if len(scratch) < self.measure_scratch_size:
            raise ValueError("scratch buffer is too small")
        if len(scratch_int) < 1:
            raise ValueError("scratch_int buffer is too small")

        trials = 2 * self.t + 1

        # Split up the scratch buffer.
        noisy_outcomes = scratch[:trials]
        noisy_scratch = scratch[trials:]

        for i in range(trials):
            self.noisy_measure(prog, data, index, noisy_outcomes[i], ancilla_1, ancilla_2,
                               noisy_scratch)
            yield

        outcome_bit = noisy_scratch[0]
        quil_classical.majority_vote(prog, noisy_outcomes, outcome_bit, scratch_int)

        # Because of the stupid thing where the QVM relies on MEASURE to initialize classical
        # registers, do a superfluous measure here of the already trashed ancilla.
        prog += gates.MEASURE(ancilla_1.qubits[0], outcome)

        # In case outcome is not a bit reference, do a CONVERT instead of a MOVE.
        prog += gates.MOVE(outcome, outcome_bit)
Esempio n. 14
0
    def test_majority_vote(self):
        test_cases = [
            ([0, 0, 0], 0),
            ([0, 0, 1], 0),
            ([0, 1, 0], 0),
            ([1, 0, 0], 0),
            ([0, 1, 1], 1),
            ([1, 0, 1], 1),
            ([1, 1, 0], 1),
            ([1, 1, 1], 1),
            ([0, 1, 0, 1, 0], 0),
            ([1, 0, 1, 0, 1], 1),
        ]

        for inputs, expected_output in test_cases:
            prog = Program()
            raw_mem = prog.declare('ro', 'BIT', len(inputs) + 1)
            raw_scratch_int = prog.declare('scratch_int', 'INTEGER', 2)
            self.initialize_memory(prog, raw_mem)
            self.initialize_memory(prog, raw_scratch_int)

            mem = MemoryChunk(raw_mem, 0, raw_mem.declared_size)
            output_mem = mem[0]
            inputs_mem = mem[1:]

            scratch_int = MemoryChunk(raw_scratch_int, 0,
                                      raw_scratch_int.declared_size)

            # Copy data from inputs into mem.
            prog += (gates.MOVE(inputs_mem[i], inputs[i])
                     for i in range(len(inputs)))

            quil_classical.majority_vote(prog, inputs_mem, output_mem,
                                         scratch_int)

            results = self.qvm.run(prog)[0]
            self.assertEqual(results[0], expected_output)
Esempio n. 15
0
 def initialize_memory(self, prog, mem):
     # Need to measure a qubit to initialize memory for some reason.
     for i in range(mem.declared_size):
         prog += gates.MEASURE(0, mem[i])
         prog += gates.MOVE(mem[i], 0)