def test_get_qubits(): pq = Program(X(0), CNOT(0, 4), MEASURE(5, [5])) assert pq.get_qubits() == {0, 4, 5} qq = pq.alloc() pq.inst(Y(2), X(qq)) assert pq.get_qubits() == {0, 1, 2, 4, 5} # this synthesizes the allocation
def test_get_qubits(): pq = Program(X(0), CNOT(0, 4), MEASURE(5, [5])) assert pq.get_qubits() == {0, 4, 5} qq = pq.alloc() pq.inst(Y(2), X(qq)) assert pq.get_qubits() == {0, 1, 2, 4, 5} # this synthesizes the allocation qubit_index = 1 p = Program(("H", qubit_index)) assert p.get_qubits() == {qubit_index} q1 = p.alloc() q2 = p.alloc() p.inst(("CNOT", q1, q2)) assert p.get_qubits() == {qubit_index, 0, 2}
def run_and_measure(self, quil_program: Program, qubits: List[int] = None, trials: int = 1) -> np.ndarray: """ Run a Quil program once to determine the final wavefunction, and measure multiple times. Alternatively, consider using ``wavefunction`` and calling ``sample_bitstrings`` on the resulting object. For a large wavefunction and a low-medium number of trials, use this function. On the other hand, if you're sampling a small system many times you might want to use ``Wavefunction.sample_bitstrings``. .. note:: If your program contains measurements or noisy gates, this method may not do what you want. If the execution of ``quil_program`` is **non-deterministic** then the final wavefunction from which the returned bitstrings are sampled itself only represents a stochastically generated sample and the outcomes sampled from *different* ``run_and_measure`` calls *generally sample different bitstring distributions*. :param quil_program: The program to run and measure :param qubits: An optional list of qubits to measure. The order of this list is respected in the returned bitstrings. If not provided, all qubits used in the program will be measured and returned in their sorted order. :param int trials: Number of times to sample from the prepared wavefunction. :return: An array of measurement results (0 or 1) of shape (trials, len(qubits)) """ if qubits is None: qubits = sorted(quil_program.get_qubits(indices=True)) return self.connection._run_and_measure(quil_program=quil_program, qubits=qubits, trials=trials, random_seed=self.random_seed)
def run_and_benchmark_program(self, prog: Program, trials: int, is_correct: Callable[[List[int]], bool], separate: bool) -> int: n_qubits = len(prog.get_qubits()) qvm = pyquil.get_qc("{}q-qvm".format(n_qubits)) # The paramaters are 10x less noisy than the defaults. qvm.qam.noise_model = self.noise_model(qvm.device) if separate: elapsed = [] results = [] for i in range(trials): start_time = time.time() trial_results = qvm.run(prog) end_time = time.time() elapsed.append(end_time - start_time) results.extend(trial_results) else: prog.wrap_in_numshots_loop(trials) start_time = time.time() results = qvm.run(prog) end_time = time.time() elapsed = end_time - start_time correct = sum(is_correct(result) for result in results) return correct, elapsed
def _run(self, circuit: Program, num_shots: int = 8192) -> List[np.ndarray]: """Run a Program a number of times and record the measurement results. Args: circuit: A Program to be run and measured. num_shots: An integer representing the number of times the quantum circuit is to be evaluated. Returns: A list of numpy arrays containing the readout of qubits from single measurements. """ prog = circuit.copy() #check whether we are given any bit-flip probabilities if self._noisy_readout_probabilities: # Add readout error to each qubit according to the dictionary for qubit in circuit.get_qubits(True): # it might happen that there are qubits without any probabilities given which would raise a KeyError try: p0, p1 = self._noisy_readout_probabilities[qubit] prog.define_noisy_readout(qubit, p00=p0, p11=p1) except: # if we don't find the key in the dictionary, we don't add readout error the the qubit, but rather pass pass # execute the circuit num_shots times prog.wrap_in_numshots_loop(num_shots) result = self._device.run(self._device.compile(prog)) return result
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 test_get_qubits(): pq = Program(X(0), CNOT(0, 4), MEASURE(5, 5)) assert pq.get_qubits() == {0, 4, 5} q = [QubitPlaceholder() for _ in range(6)] pq = Program(X(q[0]), CNOT(q[0], q[4]), MEASURE(q[5], 5)) qq = pq.alloc() pq.inst(Y(q[2]), X(qq)) assert address_qubits(pq).get_qubits() == {0, 1, 2, 3, 4} qubit_index = 1 p = Program(("H", qubit_index)) assert p.get_qubits() == {qubit_index} q1 = p.alloc() q2 = p.alloc() p.inst(("CNOT", q1, q2)) with pytest.raises(ValueError) as e: _ = address_qubits(p).get_qubits() assert e.match('Your program mixes instantiated qubits with placeholders')
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 run_and_measure( self, quil_program: Program, qubits: Optional[List[int]] = None, trials: int = 1, memory_map: Optional[Union[Dict[str, List[Union[int, float]]], Dict[MemoryReference, Any]]] = None, ) -> np.ndarray: """ Run a Quil program once to determine the final wavefunction, and measure multiple times. Alternatively, consider using ``wavefunction`` and calling ``sample_bitstrings`` on the resulting object. For a large wavefunction and a low-medium number of trials, use this function. On the other hand, if you're sampling a small system many times you might want to use ``Wavefunction.sample_bitstrings``. .. note:: If your program contains measurements or noisy gates, this method may not do what you want. If the execution of ``quil_program`` is **non-deterministic** then the final wavefunction from which the returned bitstrings are sampled itself only represents a stochastically generated sample and the outcomes sampled from *different* ``run_and_measure`` calls *generally sample different bitstring distributions*. :param quil_program: The program to run and measure :param qubits: An optional list of qubits to measure. The order of this list is respected in the returned bitstrings. If not provided, all qubits used in the program will be measured and returned in their sorted order. :param int trials: Number of times to sample from the prepared wavefunction. :param memory_map: An assignment of classical registers to values, representing an initial state for the QAM's classical memory. This is expected to be of type Dict[str, List[Union[int, float]]], where the keys are memory region names and the values are arrays of initialization data. For now, we also support input of type Dict[MemoryReference, Any], but this is deprecated and will be removed in a future release. :return: An array of measurement results (0 or 1) of shape (trials, len(qubits)) """ if qubits is None: qubits = sorted( cast(Set[int], quil_program.get_qubits(indices=True))) if memory_map is not None: quil_program = self.augment_program_with_memory_values( quil_program, memory_map) return self.connection._run_and_measure(quil_program=quil_program, qubits=qubits, trials=trials, random_seed=self.random_seed)
def test_get_qubits(): pq = Program(Declare("ro", "BIT"), X(0), CNOT(0, 4), MEASURE(5, MemoryReference("ro", 0))) assert pq.get_qubits() == {0, 4, 5} q = [QubitPlaceholder() for _ in range(6)] pq = Program(Declare("ro", "BIT"), X(q[0]), CNOT(q[0], q[4]), MEASURE(q[5], MemoryReference("ro", 0))) qq = QubitPlaceholder() pq.inst(Y(q[2]), X(qq)) assert address_qubits(pq).get_qubits() == {0, 1, 2, 3, 4} qubit_index = 1 p = Program(("H", qubit_index)) assert p.get_qubits() == {qubit_index} q1 = QubitPlaceholder() q2 = QubitPlaceholder() p.inst(("CNOT", q1, q2)) with pytest.raises(ValueError) as e: _ = address_qubits(p).get_qubits() assert e.match("Your program mixes instantiated qubits with placeholders")
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 generate_2q_single_depth_experiment(rotation: Program, depth: int, exp_type: str, measurement_qubit: int, init_one: bool = False, axis: Tuple = None) -> Program: """ A special variant of the 1q method that is specifically designed to calibrate a CPHASE gate. The ideal CPHASE is of the following form CPHASE(\phi) = diag(1,1,1,Exp[-i \phi] The imperfect CPHASE has two local Z rotations and a possible over (or under) rotation on the phase phi. Thus we have CPHASE(\Phi, \Theta_1, \Theta_2) = diag( exp(-a -b), exp(-a + b), exp(a-b), exp(a+b+c) ) a = i \Theta_1 / 2, b = i \Theta_2 / 2, c = i \Phi The following experiments isolate the three angles using the state preparations |0>|+>, |+>|0>, |1>|+>, |+>|1> where the incurred phase is measured on the qubit initialized to the plus state. The four measurements are specified by setting the measurement qubit to either q1 or q2, and setting init_one to True indicating that the non-measurement qubit be prepared in the one state |1>. :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 measurement_qubit: the qubit to be measured in this variant 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) :param init_one: True iff the non-measurement qubit should be prepared in the 1 state. :param axis: the axis of rotation. If none is specified, axis is assumed to be the Z axis. (rotation should be RZ) :return: An estimate of some aspect of the CPHASE gate which depends on the measurement variant. """ prog = Program() ro_bit = prog.declare("ro", "BIT", 1) qubits = rotation.get_qubits() non_measurement_qubit = list(qubits - {measurement_qubit})[0] prepare_state(prog, measurement_qubit) if init_one: prog.inst(X(non_measurement_qubit)) for _ in range(depth): prog.inst(rotation) if axis: prog.inst(RZ(-axis[1], measurement_qubit)) prog.inst(RY(-axis[0], measurement_qubit)) local_pauli_eig_meas(prog, exp_type, measurement_qubit) prog.measure(measurement_qubit, ro_bit) return prog
def generate_state_dfe_experiment(prog: Program, compiler) -> DFEexperiment: """ Generate a namedtuple containing all the experiments needed to perform direct fidelity estimation of a state. The experiments are represented by: input Pauli operators, whose eigenstates are the preperations; the programs specified by the user; and the output Pauli operators which specify the measurements that need to be performed. :param prog: A PyQuil program for preparing the state to be characterized. Must consist only of elements of the Clifford group. :param compiler: PyQuil compiler connection. :return: A 'DFEexperiment' """ qubits = prog.get_qubits() n_qubits = len(qubits) inpaulis = all_pauli_z_terms(n_qubits, qubits) outpaulis = [compiler.apply_clifford_to_pauli(prog, pauli) for pauli in inpaulis] return DFEexperiment(in_pauli=inpaulis, program=prog, out_pauli=outpaulis)
def apply_clifford_to_pauli(self, clifford: Program, pauli_in: PauliTerm) -> PauliTerm: r""" Given a circuit that consists only of elements of the Clifford group, return its action on a PauliTerm. In particular, for Clifford C, and Pauli P, this returns the PauliTerm representing CPC^{\dagger}. :param clifford: A Program that consists only of Clifford operations. :param pauli_in: A PauliTerm to be acted on by clifford via conjugation. :return: A PauliTerm corresponding to clifford * pauli_in * clifford^{\dagger} """ # do nothing if `pauli_in` is the identity if is_identity(pauli_in): return pauli_in indices_and_terms = list(zip(*list(pauli_in.operations_as_set()))) payload = ConjugateByCliffordRequest( clifford=clifford.out(), pauli=rpcq.messages.PauliTerm(indices=list(indices_and_terms[0]), symbols=list(indices_and_terms[1])), ) response: ConjugateByCliffordResponse = self.client.call( "conjugate_pauli_by_clifford", payload) phase_factor, paulis = response.phase, response.pauli pauli_out = PauliTerm("I", 0, 1.0j**phase_factor) clifford_qubits = clifford.get_qubits() pauli_qubits = pauli_in.get_qubits() all_qubits = sorted( set(cast(List[int], pauli_qubits)).union(set(cast(List[int], clifford_qubits)))) # The returned pauli will have specified its value on all_qubits, sorted by index. # This is maximal set of qubits that can be affected by this conjugation. for i, pauli in enumerate(paulis): pauli_out = cast(PauliTerm, pauli_out * PauliTerm(pauli, all_qubits[i])) return cast(PauliTerm, pauli_out * pauli_in.coefficient)
def generate_process_dfe_experiment(prog: Program, compiler) -> DFEexperiment: """ Generate a namedtuple containing all the experiments needed to perform direct fidelity estimation of a process. The experiments are represented by: input Pauli operators, whose eigenstates are the preperations; the programs specified by the user; and the output Pauli operators which specify the measurements that need to be performed. :param prog: A PyQuil program for preparing the unitary to be characterized. Must consist only of elements of the Clifford group. :param compiler: PyQuil compiler connection. :return: A namedtuple, called 'dfe_experiment', containing in_pauli - The Pauli being acted on by prog. program - The program the user wants to perform DFE on. out_pauli - The Pauli that should be produced after prog acts on in_prog. """ qubits = prog.get_qubits() n_qubits = len(qubits) inpaulis = all_pauli_terms(n_qubits, qubits) outpaulis = [compiler.apply_clifford_to_pauli(prog, pauli) for pauli in inpaulis] return DFEexperiment(in_pauli=inpaulis, program=prog, out_pauli=outpaulis)
def test_get_qubits_not_as_indices(): pq = Program(Declare("ro", "BIT"), X(0), CNOT(0, 4), MEASURE(5, MemoryReference("ro", 0))) assert pq.get_qubits(indices=False) == {Qubit(i) for i in [0, 4, 5]}
def test_get_qubit_placeholders(): qs = QubitPlaceholder.register(8) pq = Program(Declare("ro", "BIT"), X(qs[0]), CNOT(qs[0], qs[4]), MEASURE(qs[5], MemoryReference("ro", 0))) assert pq.get_qubits() == {qs[i] for i in [0, 4, 5]}
def run_symmetrized_readout( self, program: Program, trials: int, symm_type: int = 3, meas_qubits: Optional[List[int]] = None, ) -> np.ndarray: r""" Run a quil program in such a way that the readout error is made symmetric. Enforcing symmetric readout error is useful in simplifying the assumptions in some near term error mitigation strategies, see ``measure_observables`` for more information. The simplest example is for one qubit. In a noisy device, the probability of accurately reading the 0 state might be higher than that of the 1 state; due to e.g. amplitude damping. This makes correcting for readout more difficult. In the simplest case, this function runs the program normally ``(trials//2)`` times. The other half of the time, it will insert an ``X`` gate prior to any ``MEASURE`` instruction and then flip the measured classical bit back. Overall this has the effect of symmetrizing the readout error. The details. Consider preparing the input bitstring ``|i>`` (in the computational basis) and measuring in the Z basis. Then the Confusion matrix for the readout error is specified by the probabilities p(j|i) := Pr(measured = j | prepared = i ). In the case of a single qubit i,j \in [0,1] then: there is no readout error if p(0|0) = p(1|1) = 1. the readout error is symmetric if p(0|0) = p(1|1) = 1 - epsilon. the readout error is asymmetric if p(0|0) != p(1|1). If your quantum computer has this kind of asymmetric readout error then ``qc.run_symmetrized_readout`` will symmetrize the readout error. The readout error above is only asymmetric on a single bit. In practice the confusion matrix on n bits need not be symmetric, e.g. for two qubits p(ij|ij) != 1 - epsilon for all i,j. In these situations a more sophisticated means of symmetrization is needed; and we use orthogonal arrays (OA) built from Hadamard matrices. The symmetrization types are specified by an int; the types available are: -1 -- exhaustive symmetrization uses every possible combination of flips 0 -- trivial that is no symmetrization 1 -- symmetrization using an OA with strength 1 2 -- symmetrization using an OA with strength 2 3 -- symmetrization using an OA with strength 3 In the context of readout symmetrization the strength of the orthogonal array enforces the symmetry of the marginal confusion matrices. By default a strength 3 OA is used; this ensures expectations of the form ``<b_k . b_j . b_i>`` for bits any bits i,j,k will have symmetric readout errors. Here expectation of a random variable x as is denote ``<x> = sum_i Pr(i) x_i``. It turns out that a strength 3 OA is also a strength 2 and strength 1 OA it also ensures ``<b_j . b_i>`` and ``<b_i>`` have symmetric readout errors for any bits b_j and b_i. :param program: The program to run symmetrized readout on. :param trials: The minimum number of times to run the program; it is recommend that this number should be in the hundreds or thousands. This parameter will be mutated if necessary. :param symm_type: the type of symmetrization :param meas_qubits: An advanced feature. The groups of measurement qubits. Only these qubits will be symmetrized over, even if the program acts on other qubits. :return: A numpy array of shape (trials, len(ro-register)) that contains 0s and 1s. """ if not isinstance(symm_type, int): raise ValueError( "Symmetrization options are indicated by an int. See " "the docstrings for more information.") if meas_qubits is None: meas_qubits = list(cast(Set[int], program.get_qubits())) # It is desirable to have hundreds or thousands of trials more than the minimum trials = _check_min_num_trials_for_symmetrized_readout( len(meas_qubits), trials, symm_type) sym_programs, flip_arrays = _symmetrization(program, meas_qubits, symm_type) # Floor division so e.g. 9 // 8 = 1 and 17 // 8 = 2. num_shots_per_prog = trials // len(sym_programs) if num_shots_per_prog * len(sym_programs) < trials: warnings.warn( f"The number of trials was modified from {trials} to " f"{num_shots_per_prog * len(sym_programs)}. To be consistent with the " f"number of trials required by the type of readout symmetrization " f"chosen.") results = _measure_bitstrings(self, sym_programs, meas_qubits, num_shots_per_prog) return _consolidate_symmetrization_outputs(results, flip_arrays)
def test_get_qubits_not_as_indices(): pq = Program(X(0), CNOT(0, 4), MEASURE(5, 5)) assert pq.get_qubits(indices=False) == {Qubit(i) for i in [0, 4, 5]}
def test_get_qubit_placeholders(): qs = QubitPlaceholder.register(8) pq = Program(X(qs[0]), CNOT(qs[0], qs[4]), MEASURE(qs[5], 5)) assert pq.get_qubits() == {qs[i] for i in [0, 4, 5]}
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)
def native_quil_to_executable(self, nq_program: Program) -> Optional[Circuit]: """ Compiles a Quil program to a :class:`qscout.core.Circuit`. Because Quil does not support any form of schedule control, the entire circuit will be put in a single unscheduled block. If the :mod:`qscout.scheduler` is run on the circuit, as many as possible of those gates will be parallelized, while maintaining the order of gates that act on the same qubits. Otherwise, the circuit will be treated as a fully sequential circuit. Measurement and reset commands are supported, but only if applied to every qubit in the circuit in immediate succession. If so, they will be mapped to a prepare_all or measure_all gate. If the circuit does not end with a measurement, then a measure_all gate will be appended to it. :param pyquil.quil.Program nq_program: The program to compile. :returns: The same quantum program, converted to JaqalPaq. :rtype: qscout.core.Circuit :raises JaqalError: If the program includes a non-gate instruction other than resets or measurements. :raises JaqalError: If the user tries to measure or reset only some of the qubits, rather than all of them. :raises JaqalError: If the program includes a gate not included in `names`. """ n = max(nq_program.get_qubits()) + 1 if n > len(self._device.qubits()): raise JaqalError( "Program uses more qubits (%d) than device supports (%d)." % (n, len(self._device.qubits())) ) qsc = CircuitBuilder(native_gates=self.native_gates) block = UnscheduledBlockBuilder() qsc.expression.append(block.expression) # Quil doesn't support barriers, so either the user # won't run the the scheduler and everything will happen # sequentially, or the user will and everything can be # rescheduled as needed. qreg = qsc.register("qreg", n) block.gate("prepare_all") reset_accumulator = set() measure_accumulator = set() in_preamble = True for instr in nq_program: if reset_accumulator: if isinstance(instr, ResetQubit): reset_accumulator.add(instr.qubit.index) if nq_program.get_qubits() <= reset_accumulator: block.gate("prepare_all") reset_accumulator = set() in_preamble = False continue else: raise JaqalError( "Cannot reset only qubits %s and not whole register." % reset_accumulator ) # reset_accumulator = set() if measure_accumulator: if isinstance(instr, Measurement): measure_accumulator.add(instr.qubit.index) if nq_program.get_qubits() <= measure_accumulator: block.gate("measure_all") measure_accumulator = set() in_preamble = False continue else: raise JaqalError( "Cannot measure only qubits %s and not whole register." % measure_accumulator ) # measure_accumulator = set() if isinstance(instr, Gate): if instr.name in self.names: block.gate( self.names[instr.name], *[qreg[qubit.index] for qubit in instr.qubits], *[float(p) for p in instr.params] ) in_preamble = False else: raise JaqalError("Gate %s not in native gate set." % instr.name) elif isinstance(instr, Reset): if not in_preamble: block.gate("prepare_all") in_preamble = False elif isinstance(instr, ResetQubit): if not in_preamble: reset_accumulator = {instr.qubit.index} if nq_program.get_qubits() <= reset_accumulator: block.gate("prepare_all") reset_accumulator = set() elif isinstance(instr, Measurement): measure_accumulator = {instr.qubit.index} # We ignore the classical register. if nq_program.get_qubits() <= measure_accumulator: block.gate("measure_all") measure_accumulator = set() in_preamble = False elif isinstance(instr, Declare): pass # Ignore allocations of classical memory. else: raise JaqalError("Instruction %s not supported." % instr.out()) block.gate("measure_all", no_duplicate=True) return qsc.build()
def generate_rpe_experiment( rotation: Program, change_of_basis: Union[np.ndarray, Program], measure_qubits: Sequence[int] = None, num_depths: int = 6, prepare_and_post_select: Dict[int, int] = None) -> DataFrame: """ Generate a dataframe containing all the experiments needed to perform robust phase estimation to estimate the angle of rotation of the given rotation program. In general, this experiment consists of multiple iterations of the following steps performed for different depths and measurement in different "directions": 1) Prepare the equal superposition between computational basis states (i.e. the eigenvectors of a rotation about the Z axis) 2) Perform a change of basis which maps the computational basis to eigenvectors of the rotation. 3) Perform the rotation depth-many times, where depth=2^iteration number. Each eigenvector component picks up a phase from the rotation. In the 1-qubit case this means that the state rotates about the axis formed by the eigenvectors at a rate which is given by the relative phase between the two eigenvector components. 4) Invert the change of basis to return to the computational basis. 5) Prepare (one of) the qubit(s) for measurement along either the X or Y axis. 6) Measure this qubit, and in the multi-qubit case other qubits participating in rotation. Measure_qubits can be used e.g. in the case of noisy cross-talk to measure the effective action of some "rotation program" that acts on completely different qubits but nonetheless rotates each measure_qubit. The single qubit algorithm is due to: [RPE] Robust Calibration of a Universal Single-Qubit Gate-Set via Robust Phase Estimation Kimmel et al., Phys. Rev. A 92, 062315 (2015) https://doi.org/10.1103/PhysRevA.92.062315 https://arxiv.org/abs/1502.02677 [RPE2] Experimental Demonstration of a Cheap and Accurate Phase Estimation Rudinger et al., Phys. Rev. Lett. 118, 190502 (2017) https://doi.org/10.1103/PhysRevLett.118.190502 https://arxiv.org/abs/1702.01763 :param rotation: the program or gate whose angle of rotation is to be estimated. Note that this program will be run through forest_benchmarking.compilation.basic_compile(). :param change_of_basis: a matrix, gate, or program for the unitary change of basis transformation which maps the computational basis into the basis formed by eigenvectors of the rotation. The sign of the estimate will be determined by which computational basis states are mapped to which eigenvectors. Following the right-hand-rule convention, a rotation of RX(phi) for phi>0 about the +X axis should be paired with a change of basis maps |0> --> |+> and |1> --> |-> . This is achieved by the gate RY(pi/2, qubit). :param num_depths: the number of depths in the protocol described in [RPE]. A depth is the number of consecutive applications of the rotation in a single iteration. The maximum depth is 2**(num_depths-1) :param measure_qubits: the qubits whose angle of rotation, as a result of the action of the rotation program, RPE will attempt to estimate. These are the only qubits measured. :param prepare_and_post_select: is a bitstring used only in the multi-qubit case where one wishes to prepare the given qubits in the given classical state, and in analysis discard any results where those qubits are not observed in that state. Thus for a given prepare_and_post_select and a given measure_qubit being measured in the X or Y basis, the phase being estimated is the relative phase between the eigenvectors mapped to by the computational basis states consistent with the post_select_state and the equal superposition of the measure_qubit. For two qubits, one of the qubits may be assigned a bit, which will yield an estimate of one of the four possible phases typically estimated without post_select_state specified. :return: a dataframe populated with all of data necessary for the RPE protocol in [RPE] """ if isinstance(rotation, Gate): rotation = Program(rotation) if isinstance(change_of_basis, Gate): change_of_basis = Program(change_of_basis) rotation_qubits = rotation.get_qubits() if measure_qubits is None: measure_qubits = rotation_qubits # assume interest in qubits being rotated. # If you wish to measure multiple single qubit phases e.g. induced by cross-talk from the # operation of a gate on other qubits, consider creating multiple "dummy" experiments # that implement the identity and measure the qubits of interest. Subsequently run these # "dummies" simultaneously with an experiment whose rotation is the cross-talky program. qubits = rotation_qubits.union(measure_qubits) measure_qubits = sorted(measure_qubits) def df_dict(): for exponent in range(num_depths): depth = 2**exponent for meas_dir in ["X", "Y"]: if len(measure_qubits) > 1: # this is a >1q RPE experiment; the qubit being rotated and measured in X or # Y direction need be indicated from among the available measure qubits. for non_z_meas_qubit in measure_qubits: if prepare_and_post_select and \ non_z_meas_qubit in prepare_and_post_select.keys(): # post-selected qubits are measured only in z-basis. continue yield { "Qubits": qubits, "Rotation": rotation, "Depth": depth, "Measure Direction": meas_dir, "Measure Qubits": measure_qubits, "Non-Z-Basis Meas Qubit": non_z_meas_qubit, "Change of Basis": change_of_basis, } else: # standard 1q experiment, no need for Non-Z-Basis Meas Qubit yield { "Qubits": qubits, "Rotation": rotation, "Depth": depth, "Measure Direction": meas_dir, "Measure Qubits": measure_qubits, "Change of Basis": change_of_basis, } # TODO: Put dtypes on this DataFrame in the right way expt = DataFrame(df_dict()) if prepare_and_post_select is not None: # construct and store a post-selection state assuming the order of measure_qubits state = [None] * len(measure_qubits) for idx, q in enumerate(measure_qubits): if q in prepare_and_post_select.keys(): state[idx] = prepare_and_post_select[q] expt["Post Select State"] = [state for _ in range(expt.shape[0])] # change_of_basis is already specified as program, so add composed program column if isinstance(change_of_basis, Program): expt["Program"] = expt.apply(_make_prog_from_df, axis=1) return expt
def run_program(self, prog: Program): n_qubits = len(prog.get_qubits()) qvm = pyquil.get_qc("{}q-qvm".format(n_qubits)) return qvm.run(prog)