def generate_parametric_cz_phase_ramsey_program(qcid: int, other_qcid: int) -> Program: """ Generate a single CZ phase Ramsey experiment at a given phase. :param qcid: The qubit to move around the Bloch sphere and measure the incurred RZ on. :param other_qcid: The other qubit that constitutes a two-qubit pair along with `qcid`. :param phase: The phase kick to supply after playing the CZ pulse on the equator. :param num_shots: The number of shots to average over for the data point. :return: A parametric Program for performing a CZ Ramsey experiment. """ program = Program() # NOTE: only need readout register for `qcid` not `other_qcid` since `other_qcid` is only # needed to identify which CZ gate we're using ro = program.declare('ro', 'BIT', 1) theta = program.declare('theta', 'REAL') # go to the equator program += Program(RX(np.pi / 2, qcid)) # apply the CZ gate - note that CZ is symmetric, so the order of qubits doesn't matter program += Program(CZ(qcid, other_qcid)) # go to |1> after a phase kick program += Program(RZ(theta, qcid), RX(np.pi / 2, qcid)) program += MEASURE(qcid, ro[0]) return program
def measure_graph_state(graph: nx.Graph, focal_node: int): """Given a graph state, measure a focal node and its neighbors with a particular measurement angle. :param prep_program: Probably the result of :py:func:`create_graph_state`. :param qs: List of qubits used in prep_program or anything that can be indexed by the nodes in the graph ``graph``. :param graph: The graph state graph. This is needed to figure out what the neighbors are :param focal_node: The node in the graph to serve as the focus. The focal node is measured at an angle and all its neighbors are measured in the Z basis :return Program, list of classical offsets into the ``ro`` register. """ program = Program() theta = program.declare('theta', 'REAL') program += RY(theta, focal_node) neighbors = sorted(graph[focal_node]) ro = program.declare('ro', 'BIT', len(neighbors) + 1) program += MEASURE(focal_node, ro[0]) for i, neighbor in enumerate(neighbors): program += MEASURE(neighbor, ro[i + 1]) classical_addresses = list(range(len(neighbors) + 1)) return program, classical_addresses
def parameterized_euler_rotations( qubits: Sequence[int], *, prefix: str, suffix_alpha: str = "alpha", suffix_beta: str = "beta", suffix_gamma: str = "gamma", ) -> Program: """ Given a number of qubits (n), build a ``Program`` containing a ZXZXZ-decomposed gate on each qubit, where each ``RZ`` is parameterized by declared values with labels given by the "prefix" and "suffix" arguments. Put more plainly, the resulting Quil program on n qubits is:: RZ(alpha_label[0]) 0 RX(pi/2) 0 RZ(beta_label[0]) 0 RX(-pi/2) 0 RZ(gamma_label[0]) 0 ... RZ(alpha_label[n-1]) n-1 RX(pi/2) n-1 RZ(beta_label[0]) n-1 RX(-pi/2) n-1 RZ(gamma_label[n-1]) n-1 :param qubits: The number of qubits (n). :param prefix: The prefix for the declared memory region labels. For example, if the prefix is "preparation" and the alpha, beta, and gamma suffixes are left as default, the labels would be "preparation_alpha", "preparation_beta", and "preparation_gamma". :param suffix_alpha: The suffix for the "alpha" memory region label, which corresponds to the first (rightmost) ``Z`` in the ZXZXZ decomposition. Defaults to "alpha". :param suffix_beta: The suffix for the "beta" memory region label, which corresponds to the second (middle) ``Z`` in the ZXZXZ decomposition. Defaults to "beta". :param suffix_gamma: The suffix for the "gamma" memory region label, which corresponds to the last (leftmost) ``Z`` in the ZXZXZ decomposition. Defaults to "gamma". :return: A ``Program`` containing a 3 parameterized ``RZ``s and 2 fixed ``RX``s per qubit. """ alpha_label = f"{prefix}_{suffix_alpha}" beta_label = f"{prefix}_{suffix_beta}" gamma_label = f"{prefix}_{suffix_gamma}" p = Program() alpha = p.declare(alpha_label, "REAL", len(qubits)) beta = p.declare(beta_label, "REAL", len(qubits)) gamma = p.declare(gamma_label, "REAL", len(qubits)) for idx, q in enumerate(qubits): p += RZ(alpha[idx], q) p += RX(np.pi / 2, q) p += RZ(beta[idx], q) p += RX(-np.pi / 2, q) p += RZ(gamma[idx], q) return p
def test_subtracting_memory_regions(): # https://github.com/rigetti/pyquil/issues/709 p = Program() alpha = p.declare("alpha", "REAL") beta = p.declare("beta", "REAL") p += RZ(alpha - beta, 0) p2 = Program(p.out()) parsed_rz = p2.pop() # type: Gate parsed_param = parsed_rz.params[0] assert isinstance(parsed_param, Sub) assert parsed_param.op1 == alpha assert parsed_param.op2 == beta
def calibration_circuits( self, allow_multi_qubit: Optional[bool] = True) -> Dict[int, Program]: """Create a list of Program objects for calibrating. Args: allow_multi_qubit: A bool indicating whether we want to calibrate both qubits simultaneously. Returns: A list of Program objects for the calibration experiments. """ circuits = dict() # in case we want to calibrate both qubits simultaneously if allow_multi_qubit: # start with calibration of |0> prog = Program('PRAGMA INITIAL_REWIRING "NAIVE"') ro = prog.declare('ro', 'BIT', 2) for index, qubit in enumerate(sorted(self._qubits)): prog += Program().measure(qubit, ro[index]) circuits['0'] = prog # continue with |1> prog = Program('PRAGMA INITIAL_REWIRING "NAIVE"') ro = prog.declare('ro', 'BIT', 2) for index, qubit in enumerate(sorted(self._qubits)): prog += X(qubit) prog += Program().measure(qubit, ro[index]) circuits['1'] = prog # otherwise else: # start with |0> state for both qubits separately for index, qubit in enumerate(self._qubits): prog = Program('PRAGMA INITIAL_REWIRING "NAIVE"') ro = prog.declare('ro', 'BIT', 1) prog += Program().measure(qubit, ro[0]) circuits[str(qubit) + '0'] = prog # continue with |1>, again separately for the two qubits for index, qubit in enumerate(self._qubits): prog = Program('PRAGMA INITIAL_REWIRING "NAIVE"') ro = prog.declare('ro', 'BIT', 1) prog += X(qubit) prog += Program().measure(qubit, ro[0]) circuits[str(qubit) + '1'] = prog return circuits
def build_circuit(self, qubit_ops): program = Program() self.qubits = [program.alloc() for _ in range(len(qubit_ops))] program_readout_reg = program.declare("ro", memory_type="BIT", memory_size=len(self.qubits)) for qb, gatestr in zip(self.qubits, qubit_ops): if gatestr == "I": pass elif gatestr == "X": program.gate("X", qubits=[qb], params=[]) elif gatestr == "H": program.gate("H", qubits=[qb], params=[]) elif gatestr == "K": # our one char CX label program.gate( "CNOT", qubits=[self.qubits[self.qubits.index(qb) - 1], qb], params=[]) program.measure_all(*zip(self.qubits, program_readout_reg)) program = address_qubits(program) program.wrap_in_numshots_loop(shots=10) self.program = program
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 run_and_measure(self, program: Program, trials: int): """ Run the provided state preparation program and measure all qubits. .. note:: In contrast to :py:class:`QVMConnection.run_and_measure`, this method simulates noise correctly for noisy QVMs. However, this method is slower for ``trials > 1``. For faster noise-free simulation, consider :py:class:`WavefunctionSimulator.run_and_measure`. :param program: The state preparation program to run and then measure. :param trials: The number of times to run the program. :return: A numpy array of shape (trials, n_device_qubits) that contains 0s and 1s """ program = program.copy() for instr in program.instructions: if not isinstance(instr, Gate) and not isinstance(instr, Reset): raise ValueError( "run_and_measure programs must consist only of quantum gates." ) ro = program.declare('ro', 'BIT', max(self.device.qubit_topology().nodes) + 1) for q in sorted(self.device.qubit_topology().nodes): program.inst(MEASURE(q, ro[q])) program.wrap_in_numshots_loop(trials) executable = self.compile(program) return self.run(executable=executable)
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:])
def generate_single_depth_experiment(rotation: Program, depth: int, exp_type: str, axis: Tuple = None) -> Program: """ Generate an experiment for a single depth where the type specifies a final measurement of either X or Y. The rotation program is repeated depth number of times, and we assume the rotation is about the axis (theta, phi). :param rotation: the program specifying the gate whose angle of rotation we wish to estimate. :param depth: the number of times we apply the rotation in the experiment :param exp_type: X or Y, specifying which operator to measure at the end of the experiment :param axis: the axis of rotation. If none is specified, axis is assumed to be the Z axis. (rotation should be RZ) :return: a program specifying the entire experiment of a single iteration of the RPE protocol in [RPE] """ experiment = Program() ro_bit = experiment.declare("ro", "BIT", 1) qubit = list(rotation.get_qubits())[0] prepare_state(experiment, qubit, axis) for _ in range(depth): experiment.inst(rotation) if axis: experiment.inst(RZ(-axis[1], qubit)) experiment.inst(RY(-axis[0], qubit)) local_pauli_eig_meas(experiment, exp_type, qubit) experiment.measure(qubit, ro_bit) return experiment
def generate_single_t1_experiment(qubits: Union[int, List[int]], time: float, n_shots: int = 1000) -> Program: """ Return a t1 program in native Quil for a single time point. :param qubits: Which qubits to measure. :param time: The decay time before measurement. :param n_shots: The number of shots to average over for the data point. :return: A T1 Program. """ program = Program() try: len(qubits) except TypeError: qubits = [qubits] ro = program.declare('ro', 'BIT', len(qubits)) for q in qubits: program += RX(np.pi, q) program += Pragma('DELAY', [q], str(time)) for i in range(len(qubits)): program += MEASURE(qubits[i], ro[i]) program.wrap_in_numshots_loop(n_shots) return program
def generate_single_rabi_experiment(qubits: Union[int, List[int]], theta: float, n_shots: int = 1000) -> Program: """ Return a Rabi program in native Quil rotated through the given angle. Rabi oscillations are observed by applying successively larger rotations to the same initial state. :param qubits: Which qubits to measure. :param theta: The angle of the Rabi RX rotation. :param n_shots: The number of shots to average over for the data point. :return: A Program that rotates through a given angle about the X axis. """ program = Program() try: len(qubits) except TypeError: qubits = [qubits] ro = program.declare('ro', 'BIT', len(qubits)) for q in qubits: program += RX(theta, q) for i in range(len(qubits)): program += MEASURE(qubits[i], ro[i]) program.wrap_in_numshots_loop(n_shots) return program
def period_helper(a, N, size): c1 = QubitPlaceholder() zero = QubitPlaceholder() x = QubitPlaceholder.register(size) b = QubitPlaceholder.register(size + 1) #takes in x and b as zero, finds p = Program() n = 2 * size def_regs = Program() period_regs = def_regs.declare('ro', 'BIT', n) #For one reg, we want H, CUA, R_i m_i, X^m_i for i in range(n - 1, -1, -1): R = Program() R += H(c1) for j in range(i - 1, -1, -1): k = i - j + 1 doit = Program() doit += RK(k)(c1).dagger() R = Program().if_then(period_regs[j], doit, I(c1)) + R R += MEASURE(c1, period_regs[i]) R += Program().if_then(period_regs[i], X(c1), I(c1)) #R = Program(H(c1)) + R R = Program(H(c1)) + UA(c1, x, b, a**(2**i), N, zero) + R p = R + p p = write_in(1, x) + p p = def_regs + p p = get_defs() + p p = address_qubits(p) return p
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 parameterized_bitstring_prep(qubits: Sequence[int], reg_name: str, append_measure: bool = False, in_x_basis: bool = False) -> Program: """ Produces a parameterized program for the given group of qubits, where each qubit is prepared in the 0 or 1 state depending on the parameterization specified at run-time. See also bitstring_prep which produces a non-parameterized program that prepares and measures a single pre-specified bitstring on the given qubits. Parameterization allows for a single program to measure each bitstring (specified at run-time) and speeds up the collective measurements of all bitstring for a group of qubits. Note that the program produced by bitstring_prep does not execute any gates when preparing 0 on a particular qubit and executes only one gate to prepare 1; meanwhile, this method produces a program which executes three gates for either preparation on each qubit. :param qubits: labels of qubits on which some bitstring will be prepared and, perhaps, measured :param reg_name: the name of the register that will hold the bitstring parameters. :param append_measure: dictates whether to measure the qubits after preparing the bitstring :param in_x_basis: if true, prepare the bitstring in the x basis, where plus <==> zero, minus <==> one. :return: a parameterized program capable of preparing, and optionally measuring, any runtime specified bitstring on the given qubits. See estimate_joint_confusion_in_set in readout.py for example use. """ program = Program() if append_measure: ro = program.declare('ro', memory_type='BIT', memory_size=len(qubits)) bitstr_reg = program.declare(reg_name, memory_type='REAL', memory_size=len(qubits)) for idx, qubit in enumerate(qubits): program += RX(pi / 2, qubit) program += RZ(pi * bitstr_reg[idx], qubit) program += RX(-pi / 2, qubit) # if we are doing logic in X basis, follow each bit preparation with a Hadamard # H |0> = |+> and H |1> = |-> where + and - label the X basis vectors. if in_x_basis: program += H(qubit) if append_measure: program += MEASURE(qubit, ro[idx]) return program
def _generate_random_program(n_qubits, length, include_measures=False): """Randomly sample gates and arguments (qubits, angles)""" if n_qubits < 3: raise ValueError( "Please request n_qubits >= 3 so we can use 3-qubit gates.") gates = list(QUANTUM_GATES.values()) prog = Program() if include_measures: gates.append(MEASURE) # one bit of classical memory per qubit prog.declare("ro", "BIT", n_qubits) for _ in range(length): gate = random.choice(gates) possible_qubits = set(range(n_qubits)) sig = inspect.signature(gate) param_vals = [] for param in sig.parameters: if param in [ "qubit", "q1", "q2", "control", "control1", "control2", "target", "target_1", "target_2", ]: param_val = random.choice(list(possible_qubits)) possible_qubits.remove(param_val) elif param == "classical_reg": qubit = random.choice(list(possible_qubits)) param_val = MemoryReference("ro", qubit) possible_qubits.remove(qubit) elif param == "angle": param_val = random.uniform(-2 * pi, 2 * pi) else: raise ValueError("Unknown gate parameter {}".format(param)) param_vals.append(param_val) prog += gate(*param_vals) return prog
def _ansatz(): """Mimics the circuits from the appendix of the paper, with the exception of the ordering of the CNOT gates (before and after compilation they still do not match). This is probably best just left up to the compiler. For ease of reading the printed operations, the qubits are looped over several times. """ program = Program() qubits = self.qc.qubits() n_qubits = self.q var = 'theta' ro = program.declare('ro', memory_type='BIT', memory_size=n_qubits) thetas = { var + str(qubit): program.declare(var + str(qubit), memory_type='REAL') for qubit in qubits } sq = int(np.sqrt(n_qubits)) lim = n_qubits - sq - 1 for m in qubits: program += RX(thetas[var + str(m)], m) for m in qubits: m_1 = m + 1 m_sq = m + sq skip = (m_1) % sq if m_1 < n_qubits: if (m_sq >= n_qubits): program += CNOT(m, m_1) else: program += CNOT(m, m_sq) if (m < lim) and (skip != 0): program += CNOT(m, m_1) for m in qubits: program += MEASURE(m, ro[m]) print("program instructions from pyquil:\n") for instruction in program.instructions: print(instruction) return program
def uccsd_ansatz_circuit_parametric(n_orbitals, n_electrons, cq=None): """ This function returns a UCCSD variational ansatz with hard-coded gate angles. The number of orbitals specifies the number of qubits, the number of electrons specifies the initial HF reference state which is assumed was prepared. The list cq is an optional list which specifies the qubit label ordering which is to be assumed. :param int n_orbitals: number of spatial orbitals in the molecule (for building UCCSD singlet generator) :param int n_electrons: number of electrons in the molecule :param list() cq: custom qubits :return: program which prepares the UCCSD :math:`T_1 + T_2` propagator with a spin-singlet assumption. :rtype: Program """ # determine number of required parameters trotter_steps = 1 m = (2 * n_orbitals - n_electrons) * n_electrons / 4 n_parameters = int(trotter_steps * (m * (m + 3)) / 2) prog = Program() memref = prog.declare('theta', memory_type='REAL', memory_size=n_parameters) fermionic_list, indices = uccsd_singlet_generator_with_indices( 2 * n_orbitals, n_electrons) pauli_list = [] index_dict = {} for i, fermionic_term in enumerate(fermionic_list): pauli_sum = qubitop_to_pyquilpauli( jordan_wigner(normal_ordered(fermionic_term))) for term in pauli_sum: new_term = term # if the QPU has a custom lattice labeling, supplied by user through a list cq, reorder the Pauli labels. if cq is not None: new_term = term.coefficient for pauli in term: new_index = cq[pauli[0]] op = pauli[1] new_term = new_term * PauliTerm(op=op, index=new_index) pauli_list.append(new_term) # keep track of the indices in a dictionary index_dict[new_term.operations_as_set()] = indices[i] # add each term as successive exponentials (1 trotter step, and not taking into account commutation relations!) term_sets = commuting_sets(simplify_pauli_sum(PauliSum(pauli_list))) for j, terms in enumerate(term_sets): prog += exponentiate_commuting_pauli_sum_parametric( terms, index_dict, memref) return prog
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 append_measure_register(program: Program, qubits: List = None, trials: int = 10, ham: PauliSum = None) -> Program: """Creates readout register, MEASURE instructions for register and wraps in trials trials. Parameters ---------- param qubits : list List of Qubits to measure. If None, program.get_qubits() is used param trials : int The number of trials to run. param ham : PauliSum Hamiltonian to whose basis we need to switch. All terms in it must trivially commute! Returns ------- Program : program with the gate change and measure instructions appended """ base_change_gates = {'X': lambda qubit: H(qubit), 'Y': lambda qubit: RX(np.pi / 2, qubit), 'Z': lambda qubit: I(qubit)} if qubits is None: qubits = program.get_qubits() def _get_correct_gate(qubit: Union[int, QubitPlaceholder]) -> Program(): """Correct base change gate on the qubit `qubit` given `ham`""" # this is an extra function, because `return` allows us to # easily break out of loops for term in ham: if term[qubit] != 'I': return base_change_gates[term[qubit]](qubit) raise ValueError(f"PauliSum {ham} doesn't act on qubit {qubit}") # append to correct base change gates if ham is specified. Otherwise # assume diagonal basis if ham is not None: for qubit in ham.get_qubits(): program += Program(_get_correct_gate(qubit)) # create a read out register ro = program.declare('ro', memory_type='BIT', memory_size=len(qubits)) # add measure instructions to the specified qubits for i, qubit in enumerate(qubits): program += MEASURE(qubit, ro[i]) program.wrap_in_numshots_loop(trials) return program
def test_if_then_2(): # if FALSE creg, then measure 0 should give 1 prog = Program() creg = prog.declare("creg", "BIT") prog.inst(MOVE(creg, 0), X(0)) branch_a = Program(X(0)) branch_b = Program() prog.if_then(creg, branch_a, branch_b) prog += MEASURE(0, creg) qam = PyQVM(n_qubits=1, quantum_simulator_type=ReferenceWavefunctionSimulator) qam.execute(prog) assert qam.ram["creg"][0] == 1
def run_and_measure(self, program: Program, trials: int) -> Dict[int, np.ndarray]: """ Run the provided state preparation program and measure all qubits. The returned data is a dictionary keyed by qubit index because qubits for a given QuantumComputer may be non-contiguous and non-zero-indexed. To turn this dictionary into a 2d numpy array of bitstrings, consider:: bitstrings = qc.run_and_measure(...) bitstring_array = np.vstack([bitstrings[q] for q in qc.qubits()]).T bitstring_array.shape # (trials, len(qc.qubits())) .. note:: If the target :py:class:`QuantumComputer` is a noiseless :py:class:`QVM` then only the qubits explicitly used in the program will be measured. Otherwise all qubits will be measured. In some circumstances this can exhaust the memory available to the simulator, and this may be manifested by the QVM failing to respond or timeout. .. note:: In contrast to :py:class:`QVMConnection.run_and_measure`, this method simulates noise correctly for noisy QVMs. However, this method is slower for ``trials > 1``. For faster noise-free simulation, consider :py:class:`WavefunctionSimulator.run_and_measure`. :param program: The state preparation program to run and then measure. :param trials: The number of times to run the program. :return: A dictionary keyed by qubit index where the corresponding value is a 1D array of measured bits. """ program = program.copy() validate_supported_quil(program) ro = program.declare("ro", "BIT", len(self.qubits())) measure_used = isinstance(self.qam, QVM) and self.qam.noise_model is None qubits_to_measure = set( map(qubit_index, program.get_qubits()) if measure_used else self. qubits()) for i, q in enumerate(qubits_to_measure): program.inst(MEASURE(q, ro[i])) program.wrap_in_numshots_loop(trials) executable = self.compile(program) bitstring_array = self.run(executable=executable) bitstring_dict = {} for i, q in enumerate(qubits_to_measure): bitstring_dict[q] = bitstring_array[:, i] for q in set(self.qubits()) - set(qubits_to_measure): bitstring_dict[q] = np.zeros(trials) return bitstring_dict
def test_if_then(): # if TRUE creg, then measure 0 should give 0 prog = Program() creg = prog.declare('creg', 'BIT') prog.inst(MOVE(creg, 1), X(0)) branch_a = Program(X(0)) branch_b = Program() prog.if_then(creg, branch_a, branch_b) prog += MEASURE(0, creg) qam = PyQVM(n_qubits=1, quantum_simulator_type=ReferenceWavefunctionSimulator) qam.execute(prog) assert qam.ram['creg'][0] == 0
def measure_qubits(qubits: Sequence[int]) -> Program: """ Given a number of qubits (n), produce a ``Program`` with a ``MEASURE`` instruction on qubits 0 through n-1, with corresponding readout registers ro[0] through ro[n-1]. :param qubits: The number of qubits (n). :return: A ``Program`` that measures n qubits. """ p = Program() ro = p.declare("ro", "BIT", len(qubits)) for idx, q in enumerate(qubits): p += MEASURE(q, ro[idx]) return p
def test_while(): init_register = Program() classical_flag_register = init_register.declare("classical_flag_register", "BIT") init_register += MOVE(classical_flag_register, True) loop_body = Program(X(0), H(0)).measure(0, classical_flag_register) # Put it all together in a loop program: loop_prog = init_register.while_do(classical_flag_register, loop_body) qam = PyQVM(n_qubits=1, quantum_simulator_type=ReferenceWavefunctionSimulator) qam.execute(loop_prog) assert qam.ram[classical_flag_register.name][0] == 0
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 _naive_program_generator(qc: QuantumComputer, qubits: Sequence[int], permutations: np.ndarray, gates: np.ndarray) -> Program: """ Naively generates a native quil program to implement the circuit which is comprised of the given permutations and gates. :param qc: the quantum resource that will implement the PyQuil program for each model circuit :param qubits: the qubits available for the implementation of the circuit. This naive implementation simply takes the first depth-many available qubits. :param permutations: array of depth-many arrays of size n_qubits indicating a qubit permutation :param gates: a depth by depth//2 array of matrices representing the 2q gates at each layer. The first row of matrices is the earliest-time layer of 2q gates applied. :return: a PyQuil program in native_quil instructions that implements the circuit represented by the input permutations and gates. Note that the qubits are measured in the proper order such that the results may be directly compared to the simulated heavy hitters from collect_heavy_outputs. """ num_measure_qubits = len(permutations[0]) # at present, naively select the minimum number of qubits to run on qubits = qubits[:num_measure_qubits] # create a simple program that uses the compiler to directly generate 2q gates from the matrices prog = Program() for layer_idx, (perm, layer) in enumerate(zip(permutations, gates)): for gate_idx, gate in enumerate(layer): # get the Quil definition for the new gate g_definition = DefGate( "LYR" + str(layer_idx) + "_RAND" + str(gate_idx), gate) # get the gate constructor G = g_definition.get_constructor() # add definition to program prog += g_definition # add gate to program, acting on properly permuted qubits prog += G(int(qubits[perm[gate_idx]]), int(qubits[perm[gate_idx + 1]])) ro = prog.declare("ro", "BIT", len(qubits)) for idx, qubit in enumerate(qubits): prog.measure(qubit, ro[idx]) native_quil = qc.compiler.quil_to_native_quil(prog) if not set(native_quil.get_qubits()).issubset(set(qubits)): raise ValueError( "naive_program_generator could not generate program using only the " "qubits supplied. Please provide your own program_generator if you wish " "to use only the qubits specified.") return native_quil
def parameterized_readout_symmetrization(qubits: Sequence[int], label: str = "symmetrization") -> Program: """ Given a number of qubits (n), produce a ``Program`` with an ``RX`` instruction on qubits 0 through n-1, parameterized by memory regions label[0] through label[n-1], where "label" defaults to "symmetrization". :param qubits: The number of qubits (n). :param label: The name of the declared memory region. :return: A ``Program`` with parameterized ``RX`` gates on n qubits. """ p = Program() symmetrization = p.declare(f"{label}", "REAL", len(qubits)) for idx, q in enumerate(qubits): p += RX(symmetrization[idx], q) return p
def test_measure_all(): p = Program() mem = p.declare("ro", memory_size=4) p.measure_all((0, mem[0]), (1, mem[1]), (2, mem[3])) assert p.out( ) == "DECLARE ro BIT[4]\nMEASURE 0 ro[0]\nMEASURE 1 ro[1]\nMEASURE 2 ro[3]\n" p = Program([H(idx) for idx in range(4)]) p.measure_all() for idx in range(4): assert p[idx + 5] == MEASURE(idx, MemoryReference("ro", idx)) p = Program() p.measure_all() assert p.out() == ""
def read_out(p, reg): p = Program(p) ro = p.declare('ro', 'BIT', len(reg)) for i in range(len(reg)): p += MEASURE(reg[i], ro[i]) p = address_qubits(p) result = qvm.run(p) outp = 0 for i in range(len(result[0])): if (result[0][i] == 1): outp += 2**i return (outp, p)
def __init__(self, qc, file_prefix, num_bits, hamil, all_var_nums, fun_name_to_fun, do_resets=True, **kwargs): """ Constructor Do in constructor as much hamil indep stuff as possible so don't have to redo it with every call to cost fun. Also, when self.num_samples !=0, we store a dict called term_to_exec mapping an executable (output of Rigetti compile() function) to a term, for each term in the hamiltonian hamil. When num_samples=0, term_to_exec={} Parameters ---------- qc : QuantumComputer file_prefix : str num_bits : int hamil : QubitOperator all_var_nums : list[int] fun_name_to_fun : dict[str, function] do_resets : bool kwargs : dict key-words args of MeanHamilMinimizer constructor Returns ------- """ MeanHamil.__init__(self, file_prefix, num_bits, hamil, all_var_nums, fun_name_to_fun, **kwargs) self.qc = qc self.do_resets = do_resets # this creates a file with all PyQuil gates that # are independent of hamil. Gates may contain free parameters self.translator = Qubiter_to_RigettiPyQuil( self.file_prefix, self.num_bits, aqasm_name='RigPyQuil', prelude_str='', ending_str='') with open(self.translator.aqasm_path, 'r') as fi: self.translation_line_list = fi.readlines() pg = Program() self.pg = pg if self.num_samples: # pg prelude pg += Pragma('INITIAL_REWIRING', ['"PARTIAL"']) if self.do_resets: pg += RESET() ro = pg.declare('ro', 'BIT', self.num_bits) s = '' for var_num in self.all_var_nums: vname = self.translator.vprefix + str(var_num) s += vname s += ' = pg.declare("' s += vname s += '", memory_type="REAL")\n' exec(s) # add to pg the operations that are independent of hamil for line in self.translation_line_list: line = line.strip('\n') if line: exec(line) len_pg_in = len(pg) # hamil loop to store executables for each term in hamil self.term_to_exec = {} for term, coef in self.hamil.terms.items(): # reset pg to initial length. # Temporary work-around to bug # in PyQuil ver 2.5.0. # Slicing was changing # pg from type Program to type list pg = Program(pg[:len_pg_in]) self.pg = pg # add xy measurements coda to pg bit_pos_to_xy_str =\ {bit: action for bit, action in term if action != 'Z'} RigettiTools.add_xy_meas_coda_to_program( pg, bit_pos_to_xy_str) # request measurements for i in range(self.num_bits): pg += MEASURE(i, ro[i]) pg.wrap_in_numshots_loop(shots=self.num_samples) executable = self.qc.compile(pg) # print(",,,...", executable) self.term_to_exec[term] = executable