Ejemplo n.º 1
0
    def test_constructor(self):
        chunk = MemoryChunk(self.mem, 10, 20)
        self.assertEqual(chunk.start, 10)
        self.assertEqual(chunk.end, 20)

        with self.assertRaises(IndexError):
            MemoryChunk(self.mem, 0, 21)
Ejemplo n.º 2
0
def new_logical_qubit(prog: Program, qecc: QECC, name: str) -> CodeBlock:
    n = qecc.n
    raw_mem = prog.declare(name, 'BIT', 2 * n)
    mem = MemoryChunk(raw_mem, 0, raw_mem.declared_size)
    qubits = [QubitPlaceholder() for _ in range(n)]
    _initialize_memory(prog, raw_mem, qubits)
    return CodeBlock(qubits, mem[:n], mem[n:])
Ejemplo n.º 3
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)
Ejemplo n.º 4
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]))
Ejemplo n.º 5
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)
Ejemplo n.º 6
0
    def noisy_measure(self, prog: Program, data: CodeBlock, index: int, outcome: MemoryReference,
                      ancilla_1: CodeBlock, ancilla_2: CodeBlock, scratch: 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 not fault tolerant and may fail if any single operation fails.
        """
        n, r_2 = self.n, self.r_2

        if index != 0:
            raise ValueError("only one logical qubit per code block")
        if data.n != n:
            raise ValueError("data code word is of incorrect size")
        if ancilla_1.n != n:
            raise ValueError("ancilla_1 code word is of incorrect size")
        if ancilla_2.n != n:
            raise ValueError("ancilla_2 code word is of incorrect size")
        if len(scratch) < self.error_correct_scratch_size:
            raise ValueError("scratch buffer is too small")

        # Reset the ancilla to |0>.
        self.encode_zero(prog, ancilla_1, ancilla_2, scratch)

        # Split up the scratch buffer.
        mem = scratch[:n]
        scratch = scratch[n:(n + r_2 + 2)]

        # Propagate each Z in the operator from data block to ancilla block for each, then measure
        # in the Z basis.
        #
        # This implements the technique from section 3 of
        # "Efficient fault-tolerant quantum computing" by Andrew M. Steane.
        prog += apply_transversally(gates.CNOT, data.qubits, ancilla_1.qubits)
        prog += (gates.MEASURE(ancilla_1.qubits[i], mem[i]) for i in range(self.n))

        # Opportunistically correct any X errors.
        quil_classical_correct(prog, mem, data.x_errors, scratch,
                               self.parity_check_c2, self._c2_syndromes)

        # Finally, compute the measurement outcome.
        z_operator = self.z_operator_matrix()[index:(index + 1), :]
        outcome_chunk = MemoryChunk(
            MemoryReference(outcome.name), outcome.offset, outcome.offset + 1
        )
        quil_classical.matmul(prog, z_operator, mem, outcome_chunk, scratch)
Ejemplo n.º 7
0
    def test_getitem_slice(self):
        chunk = MemoryChunk(self.mem, 10, 20)

        sub_chunk = chunk[2:9]
        self.assertIsInstance(sub_chunk, MemoryChunk)
        self.assertEqual(sub_chunk.start, 12)
        self.assertEqual(sub_chunk.end, 19)

        sub_chunk = chunk[:9]
        self.assertIsInstance(sub_chunk, MemoryChunk)
        self.assertEqual(sub_chunk.start, 10)
        self.assertEqual(sub_chunk.end, 19)

        sub_chunk = chunk[2:]
        self.assertIsInstance(sub_chunk, MemoryChunk)
        self.assertEqual(sub_chunk.start, 12)
        self.assertEqual(sub_chunk.end, 20)
Ejemplo n.º 8
0
def _make_error_corrector(
        prog: Program, qecc: QECC, ancilla_1: CodeBlock,
        ancilla_2: CodeBlock) -> Callable[[List[CodeBlock]], None]:
    # All error corrections share the same ancilla qubits and classical memory
    # chunk. This limits parallelism, which significantly reduces fault tolerance.
    # However, keeping the number of ancilla qubits low is necessary in order to
    # have any chance of simulating with the QVM.

    # Classical scratch BIT registers for error correction.
    scratch_size = max(qecc.n, qecc.error_correct_scratch_size)
    raw_scratch = prog.declare('error_correct_scratch', 'BIT', scratch_size)
    scratch = MemoryChunk(raw_scratch, 0, raw_scratch.declared_size)
    _initialize_memory(prog, raw_scratch, ancilla_1.qubits + ancilla_1.qubits)

    def perform_error_correction(logical_qubits: List[CodeBlock]):
        for block in logical_qubits:
            qecc.error_correct(prog, block, ancilla_1, ancilla_2, scratch)

    return perform_error_correction
Ejemplo n.º 9
0
def rewrite_program(raw_prog: Program, qecc: QECC) -> Program:
    if qecc.k != 1:
        raise UnsupportedQECCError("code must have k = 1")

    if raw_prog.defined_gates:
        raise UnsupportedProgramError("does not support DEFGATE")

    # Assign indices to qubit placeholders in the raw program.
    raw_prog = quil.address_qubits(raw_prog)

    new_prog = Program()

    logical_qubits = {
        index: new_logical_qubit(new_prog, qecc,
                                 "logical_qubit_{}".format(index))
        for index in raw_prog.get_qubits(indices=True)
    }

    # Construct ancilla code blocks.
    ancilla_1 = new_logical_qubit(new_prog, qecc, "ancilla_1")
    ancilla_2 = new_logical_qubit(new_prog, qecc, "ancilla_2")

    # Classical scratch BIT registers for gates/measurements.
    scratch_size = max(qecc.n, qecc.measure_scratch_size)
    raw_scratch = new_prog.declare('scratch', 'BIT', scratch_size)
    scratch = MemoryChunk(raw_scratch, 0, raw_scratch.declared_size)
    _initialize_memory(new_prog, raw_scratch,
                       ancilla_1.qubits + ancilla_2.qubits)

    # Classical scratch INTEGER registers.
    raw_scratch_int = new_prog.declare('scratch_int', 'INTEGER', 2)
    scratch_int = MemoryChunk(raw_scratch_int, 0,
                              raw_scratch_int.declared_size)
    _initialize_memory(new_prog, raw_scratch_int,
                       ancilla_1.qubits + ancilla_2.qubits)

    perform_error_correction = _make_error_corrector(new_prog, qecc, ancilla_1,
                                                     ancilla_2)

    # Reset all logical qubits.
    for block in logical_qubits.values():
        qecc.encode_zero(new_prog, block, ancilla_1, scratch)

    for inst in raw_prog.instructions:
        if isinstance(inst, Gate):
            gate_qubits = [
                logical_qubits[index] for index in _gate_qubits(inst)
            ]
            qecc.apply_gate(new_prog, inst.name, *gate_qubits)

            # Perform error correction after every logical gate.
            perform_error_correction(logical_qubits.values())
        elif isinstance(inst, Measurement):
            qubit = logical_qubits[_extract_qubit_index(inst.qubit)]
            # This should really use its own ancilla instead of sharing with the error correction,
            # but we need be extremely conservative with the number of qubits.
            for _ in qecc.measure(new_prog, qubit, 0, inst.classical_reg,
                                  ancilla_1, ancilla_2, scratch, scratch_int):
                # Since measurements are taken multiple times for redundancy, we need to perform
                # rounds of error correction during the measurement routine.
                perform_error_correction(logical_qubits.values())
        elif isinstance(inst, ResetQubit):
            raise NotImplementedError(
                "this instruction is not in the Quil spec")
        elif isinstance(inst, JumpTarget):
            new_prog.inst(JumpTarget(_mangle_label(inst.label)))
        elif isinstance(inst, JumpConditional):
            new_prog.inst(
                type(inst)(_mangle_label(inst.target), inst.condition))
        elif isinstance(inst, Jump):
            new_prog.inst(Jump(_mangle_label(inst.target)))
        elif isinstance(inst, Halt):
            new_prog.append(inst)
        elif isinstance(inst, Wait):
            raise NotImplementedError()
        elif isinstance(inst, Reset):
            for block in logical_qubits.values():
                qecc.encode_zero(new_prog, block.qubits, ancilla_1, scratch)
        elif isinstance(inst, Declare):
            new_prog.inst(inst)
        elif isinstance(inst, Pragma):
            new_prog.inst(inst)
        elif any(
                isinstance(inst, ClassicalInst)
                for ClassicalInst in CLASSICAL_INSTRUCTIONS):
            new_prog.inst(inst)
        else:
            raise UnsupportedProgramError("unsupported instruction: {}", inst)

    return quil.address_qubits(new_prog)
Ejemplo n.º 10
0
    def test_getitem_single(self):
        chunk = MemoryChunk(self.mem, 10, 20)

        item = chunk[5]
        self.assertIsInstance(item, MemoryReference)
        self.assertEqual(item, self.mem[15])
Ejemplo n.º 11
0
 def test_len(self):
     chunk = MemoryChunk(self.mem, 1, 10)
     self.assertEqual(len(chunk), 9)