def test_separate_measures(self): prog = Program() qreg = prog.qalloc(3) creg = prog.calloc(3) prog.apply(H, qreg[0]) prog.apply(H, qreg[1]) prog.apply(H, qreg[2]) expected = prog.to_circ() result = Prg() cbs = result.declare("ro", "BIT", 3) result += pg.H(0) result += pg.H(1) result += pg.H(2) result += pg.MEASURE(0, cbs[0]) result += pg.MEASURE(1, cbs[1]) result += pg.MEASURE(2, cbs[2]) result, to_measure = pyquil_to_qlm(result, True) exp_str = print_aq(expected) res_str = print_aq(result) self.assertEqual(res_str, exp_str) self.assertEqual(to_measure, [0, 1, 2])
def superdense_coding_program(self, bit0: int, bit1: int): raw_prog = Program() ro = raw_prog.declare('ro', 'BIT', 2) # Prepare Bell pair raw_prog += gates.H(0) raw_prog += gates.CNOT(0, 1) # Alice controls qubit 0 and Bob controls 1 if bit0 == 0 and bit1 == 0: pass if bit0 == 0 and bit1 == 1: raw_prog += gates.X(0) if bit0 == 1 and bit1 == 0: raw_prog += gates.Z(0) if bit0 == 1 and bit1 == 1: raw_prog += gates.X(0) raw_prog += gates.Z(0) # Now Alice sends qubit 0 to Bob # Bob rotates from Bell basis to standard basis raw_prog += gates.CNOT(0, 1) raw_prog += gates.H(0) # Measure qubits into Bob's registers raw_prog += gates.MEASURE(0, ro[0]) raw_prog += gates.MEASURE(1, ro[1]) new_prog = ftqc.rewrite_program(raw_prog, self.steane_7bit) results = self.run_program(new_prog) for result in results: self.assertEqual(result[0], bit0) self.assertEqual(result[1], bit1)
def error_correct(self, prog: Program, data: CodeBlock, ancilla_1: CodeBlock, ancilla_2: CodeBlock, scratch: MemoryChunk): """ Extend a Quil program to perform error correction. """ 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.error_correct_scratch_size: raise ValueError("scratch buffer is too small") # Split up the scratch buffer. mem = scratch[:self.n] correct_scratch = scratch[self.n:] # See section 4.4 of "An Introduction to Quantum Error Correction and Fault-Tolerant Quantum # Computation" by Daniel Gottesman for correction circuit. # Propagate X errors from data block to ancilla block, then measure in the Z basis. self.encode_plus(prog, ancilla_1, ancilla_2, scratch) 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)) quil_classical_correct(prog, mem, data.x_errors, correct_scratch, self.parity_check_c2, self._c2_syndromes) # Propagate Z errors from data block to ancilla block, then measure in the X basis. self.encode_zero(prog, ancilla_1, ancilla_2, scratch) prog += apply_transversally(gates.CNOT, ancilla_1.qubits, data.qubits) prog += apply_transversally(gates.H, ancilla_1.qubits) prog += (gates.MEASURE(ancilla_1.qubits[i], mem[i]) for i in range(self.n)) quil_classical_correct(prog, mem, data.z_errors, correct_scratch, self.parity_check_c1, self._c1_syndromes)
def qlm_to_pyquil(qlm_circuit, program_pragma=None): """ Converts a QLM circuit to a pyquil circuit Args: qlm_circuit: QLM circuit to convert Returns: Pyquil circuit """ if program_pragma is not None: p = Program(program_pragma) else: p = Program() creg = p.declare("ro", "BIT", qlm_circuit.nbcbits) for op in qlm_circuit.ops: if op.type == 0: qubits = build_qbits(op.qbits) p += build_gate(qlm_circuit.gateDic, op.gate, qubits) elif op.type == 1: for qb, cb in zip(op.qbits, op.cbits): p += pg.MEASURE(qb, creg[cb]) # Adding measures to unify interface for qb, cbit in enumerate(creg): p += pg.MEASURE(qb, cbit) return p
def test_multiple_measurements_program(self): raw_prog = Program() ro = raw_prog.declare('ro', 'BIT', 2) raw_prog += gates.H(0) raw_prog += gates.MEASURE(0, ro[0]) raw_prog.if_then(ro[0], gates.X(0), Program()) raw_prog += gates.MEASURE(0, ro[1]) new_prog = ftqc.rewrite_program(raw_prog, self.steane_7bit) results = self.run_program(new_prog) for result in results: self.assertEqual(result[1], 0)
def _error_detect_z(self, prog: Program, data: CodeBlock, ancilla: CodeBlock, outcome: MemoryReference, scratch: MemoryChunk, include_operators: bool): """ Extend a Quil program to perform detection of Z errors on a data block. This this uses noisy preparation of the ancilla for measurement, meaning the procedure is not totally reliable. """ if len(scratch) < (self.n + self.r_1 + 2): raise ValueError("scratch buffer is too small") # Split up the scratch buffer. mem = scratch[:self.n] scratch = scratch[self.n:] # Prepare a noisy ancilla. If measuring X operators as well, prepare in X eigenstate, # otherwise prepare in Z eigenstate. ancilla.reset(prog) if include_operators: prog += self.noisy_encode_plus(ancilla.qubits) else: prog += self.noisy_encode_zero(ancilla.qubits) # Propagate Z errors from data block to ancilla block, then measure in the X basis. prog += apply_transversally(gates.CNOT, ancilla.qubits, data.qubits) prog += apply_transversally(gates.H, ancilla.qubits) prog += (gates.MEASURE(ancilla.qubits[i], mem[i]) for i in range(self.n)) # Perform classical error detection with parity check matrix and maybe X operators. check_matrix = self.parity_check_c1 if include_operators: check_matrix = np.concatenate([check_matrix, self.x_operator_matrix()], axis=0) quil_classical_detect(prog, mem, data.z_errors, outcome, scratch, check_matrix)
def test_X_fidelity(self): raw_prog = Program() ro = raw_prog.declare('ro', 'BIT', 1) raw_prog += gates.X(0) raw_prog += gates.X(0) raw_prog += gates.X(0) raw_prog += gates.MEASURE(0, ro[0]) ft_prog = ftqc.rewrite_program(raw_prog, self.steane_7bit) is_correct = lambda result: result[0] == 1 trials = 100000 correct, elapsed = self.run_and_benchmark_program(raw_prog, trials, is_correct, separate=False) print(correct, trials, elapsed) trials = 20 correct, elapsed = self.run_and_benchmark_program(ft_prog, trials, is_correct, separate=True) print(correct, trials, elapsed)
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))
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)
def test_single_Z_program(self): raw_prog = Program() ro = raw_prog.declare('ro', 'BIT', 1) raw_prog += gates.Y(0) raw_prog += gates.MEASURE(0, ro[0]) new_prog = ftqc.rewrite_program(raw_prog, self.steane_7bit) results = self.run_program(new_prog) for result in results: self.assertEqual(result[0], 1)
def test_measures(self): # Create qlm program prog = Program() qreg = prog.qalloc(3) prog.apply(H, qreg[0]) prog.apply(H, qreg[1]) prog.apply(H, qreg[2]) result = qlm_to_pyquil(prog.to_circ()) # Create pyquil program expected = Prg() cbs = expected.declare("ro", "BIT", 3) expected += pg.H(0) expected += pg.H(1) expected += pg.H(2) expected += pg.MEASURE(0, cbs[0]) expected += pg.MEASURE(1, cbs[1]) expected += pg.MEASURE(2, cbs[2]) self.assertEqual(str(result), str(expected))
def test_default_gates(self): # Create qlm program prog = Program() qreg = prog.qalloc(3) for op in pygates_1qb: prog.apply(op, qreg[0]) for op in pygates_2qb: prog.apply(op, qreg[0], qreg[1]) prog.apply(CCNOT, qreg[0], qreg[1], qreg[2]) qlm_circuit = prog.to_circ() result = qlm_to_pyquil(qlm_circuit) # Create pyquil program expected = Prg() expected_creg = expected.declare("ro", "BIT", 3) for op in quil_1qb: expected += op(0) for op in quil_params: expected += op(3.14, 0) expected += pg.SWAP(0, 1) expected += pg.CNOT(0, 1) for op in quil_ctrl: expected += op(1).controlled(0) for op in quil_ctrl_prm: expected += op(3.14, 1).controlled(0) expected += pg.CCNOT(0, 1, 2) expected += pg.MEASURE(0, expected_creg[0]) expected += pg.MEASURE(1, expected_creg[1]) expected += pg.MEASURE(2, expected_creg[2]) self.assertEqual(str(result), str(expected))
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)
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)
def test_recursive_ctrl_and_dagger(self): # Create qlm program prog = Program() qreg = prog.qalloc(5) prog.apply(Y.ctrl().ctrl().ctrl().ctrl().dag().dag().dag(), *qreg) qlm_circuit = prog.to_circ() result = qlm_to_pyquil(qlm_circuit) # Create pyquil program expected = Prg() expected_creg = expected.declare("ro", "BIT", 5) expected += (pg.Y(4).controlled(0).controlled(1).controlled( 2).controlled(3).dagger()) for qbit, cbit in enumerate(expected_creg): expected += pg.MEASURE(qbit, cbit) self.assertEqual(str(result), str(expected))
def MEASURE(qubit) -> Addr: program_context().inst(gates.MEASURE(qubit, qubit)) return Addr(qubit)
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)
qvm = api.QVMConnection() # ============================================================================= # teleportation circuit # ============================================================================= # Alice wants to send |1> to Bob qprog += gates.X(0) # main circuit qprog += [ gates.H(1), gates.CNOT(1, 2), gates.CNOT(0, 1), gates.H(0), gates.MEASURE(0, creg[0]), gates.MEASURE(1, creg[1]) ] # conditional operations qprog.if_then(creg[0], gates.Z(2)) qprog.if_then(creg[1], gates.X(2)) # measure qubit three qprog.measure(2, creg[2]) # ============================================================================= # run the circuit and print the results. Note Bob always measures 1 # ============================================================================= print(qvm.run(qprog))
"""Random bit generator circuit in PyQuil 2.1.1.""" # imports from pyquil.quil import Program import pyquil.gates as gates from pyquil import api # get a program and classical memory register qprog = Program() creg = qprog.declare(name="ro", memory_size=1) # REQUIRES: api key, qvm running in background ("qvm -S" in a linux terminal # after it is installed. See Rigetti website for download instructions # https://www.rigetti.com/forest) qvm = api.QVMConnection() # add instructions to the program qprog += [gates.H(0), gates.MEASURE(0, creg[0])] print(qprog) print(qvm.run(qprog, trials=1))
# Add the right rotation + measurement operators to the ansatz circuit = ansatz.copy() qubits = computer.qubits() to_measure = [] for (q, p) in enumerate(squashed): if p in ("X", "Y", "Z"): to_measure.append(q) if p == "X": circuit += [gates.H(qubits[q])] elif p == "Y": circuit += [gates.S(qubits[q]), gates.H(qubits[q])] # Add the terminal measurements # Note we do it this way since all measurements *must* be at the end of the circuit on hardware for q in to_measure: circuit += [gates.MEASURE(qubits[q], creg[q])] # Execute the circuit circuit.wrap_in_numshots_loop(shots) executable = computer.compile(circuit) res = computer.run(executable, memory_map={"theta": angles}) # Do the postprocessing supports = [support(pauli) for pauli in paulis] total = 0.0 for ii in range(len(supports)): tot = 0.0 for vals in res: sliced = islice(vals, supports[ii]) tot += (-1)**sum(sliced) total += coeffs[ii] * tot / shots
def expectation(angles: List[float], coeff: complex, pauli: str, ansatz: pyquil.Program, creg: pyquil.quilatom.MemoryReference, computer: pyquil.api.QuantumComputer, shots: int = 10000, verbose: bool = False) -> float: """Returns coeff * <\theta| paulii |\theta>. Args: angles: List of angles at which to evaluate coeff * <theta| pauli |theta>. coeff: Coefficient of Pauli term. pauli: Pauli string. ansatz: pyQuil program representing the ansatz state. creg: Classical register of ansatz to measure into. computer: QuantumComputer to execute the circuit on. shots: Number of times to execute the circuit (sampling statistics). verbose: Option for visualization/debugging. """ if np.isclose(np.imag(coeff), 0.0): coeff = np.real(coeff) if set(pauli) == {"I"}: return coeff angles = list(angles) angles = deepcopy(angles) if verbose: print("DEBUG holy f**k") print(f"type(angles) = {type(angles)}") print("angles =", angles) # Set up the circuit circuit = ansatz.copy() qubits = computer.qubits() measured = [] for (q, p) in enumerate(pauli): if p in ("X", "Y", "Z"): measured.append(qubits[q]) if p == "X": circuit += [gates.H(qubits[q]), gates.MEASURE(qubits[q], creg[q])] elif p == "Y": circuit += [ gates.S(qubits[q]), gates.H(qubits[q]), gates.MEASURE(qubits[q], creg[q]) ] elif p == "Z": circuit += [gates.MEASURE(qubits[q], creg[q])] if verbose: print(f"Computing {coeff} x <theta|{pauli}|theta>...") print("\nCircuit to be executed:") print(circuit) print(f"type(angles) = {type(angles)}") # Execute the circuit circuit.wrap_in_numshots_loop(shots) executable = computer.compile(circuit) res = computer.run(executable, memory_map={"theta": angles}) if verbose: print("\nResults:") print(f"{len(res)} total measured bit strings.") print(res) # Do the postprocessing tot = 0.0 for vals in res: tot += (-1)**sum(vals) return coeff * tot / shots
def MEASURE(qubit) -> MemoryReference: program_context().inst(gates.MEASURE(qubit, MemoryReference("ro", qubit))) return MemoryReference("ro", qubit)
# ============================================================================= # circuit to test simulator # ============================================================================= # main (arbitrary) circuit for level in range(depth): for ii in range(n): qprog.inst(gates.H(ii), gates.X(ii)) if ii != 0: qprog.inst(gates.CNOT(ii, 0)) # measurements for ii in range(n): qprog.inst(gates.MEASURE(ii, creg[ii])) # ============================================================================= # run the circuit and print the results # ============================================================================= # timing -- get the start time start = time.time() output = qvm.run(qprog) # timing -- get the end time runtime = time.time() - start # print out the runtime print(n, depth, runtime)