def _symmetrization( program: Program, meas_qubits: List[int], symm_type: int = 3) -> Tuple[List[Program], List[Tuple[bool]]]: """ For the input program generate new programs which flip the measured qubits with an X gate in certain combinations in order to symmetrize readout. An expanded list of programs is returned along with a list of bools which indicates which qubits are flipped in each program. 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 programs: a program which will be symmetrized. :param meas_qubits: the groups of measurement qubits. Only these qubits will be symmetrized over, even if the program acts on other qubits. :param sym_type: an int determining the type of symmetrization performed. :return: a list of symmetrized programs, the corresponding array of bools indicating which qubits were flipped. """ if symm_type < -1 or symm_type > 3: raise ValueError( "symm_type must be one of the following ints [-1, 0, 1, 2, 3].") elif symm_type == -1: # exhaustive = all possible binary strings flip_matrix = np.asarray( list(itertools.product([0, 1], repeat=len(meas_qubits)))) elif symm_type >= 0: flip_matrix = _construct_orthogonal_array(len(meas_qubits), symm_type) # The next part is not rigorous in the sense that we simply truncate to the desired # number of qubits. The problem is that orthogonal arrays of a certain strength for an # arbitrary number of qubits are not known to exist. flip_matrix = flip_matrix[:, :len(meas_qubits)] symm_programs = [] flip_arrays = [] for flip_array in flip_matrix: total_prog_symm = program.copy() prog_symm = _flip_array_to_prog(flip_array, meas_qubits) total_prog_symm += prog_symm symm_programs.append(total_prog_symm) flip_arrays.append(flip_array) return symm_programs, flip_arrays
def _get_flipped_protoquil_program(program: Program) -> Program: """For symmetrization, generate a program where X gates are added before measurement. Forest 1.3 is really picky about where the measure instructions happen. It has to be at the end! """ program = program.copy() to_measure = [] while len(program) > 0: inst = program.instructions[-1] if isinstance(inst, Measurement): program.pop() to_measure.append((inst.qubit, inst.classical_reg)) else: break program += Pragma('PRESERVE_BLOCK') for qu, addr in to_measure[::-1]: program += RX(pi, qu) program += Pragma('END_PRESERVE_BLOCK') for qu, addr in to_measure[::-1]: program += Measurement(qubit=qu, classical_reg=addr) return program
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 _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 _evaluate_single_term(self, ansatz_circuit: Program, meas_term: PauliTerm) -> np.ndarray: """Compute the expectation value of a single PauliTerm , given a quantum circuit to evaluate on. Args: ansatz_circuit: A Program representing the quantum state to evaluate the PauliTerm on. meas_term: a PauliTerm object to be evaluated. """ # First, create the quantum circuit needed for evaluation. # concatenate operator (converted to a Program) and ansatz circuit expectation_circuit = ansatz_circuit.copy() expectation_circuit += meas_term.program ro = expectation_circuit.declare('ro', 'BIT', len(meas_term.get_qubits())) # add necessary post-rotations and measurement for i, qubit in enumerate(sorted(list(meas_term.get_qubits()))): if meas_term.pauli_string([qubit]) == 'X': expectation_circuit += H(qubit) elif meas_term.pauli_string([qubit]) == 'Y': expectation_circuit += H(qubit) expectation_circuit += S(qubit) expectation_circuit += Program().measure(qubit, ro[i]) result = self._run(expectation_circuit, num_shots=self._num_shots_evaluation) return result
def test_copy(): prog1 = Program(H(0), CNOT(0, 1)) prog2 = prog1.copy().measure_all() assert prog1.out() == "\n".join(["H 0", "CNOT 0 1", ""]) assert prog2.out() == "\n".join([ "H 0", "CNOT 0 1", "DECLARE ro BIT[2]", "MEASURE 0 ro[0]", "MEASURE 1 ro[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_copy(): prog1 = Program( H(0), CNOT(0, 1), ) prog2 = prog1.copy().measure_all() assert prog1.out() == '\n'.join(['H 0', 'CNOT 0 1', '']) assert prog2.out() == '\n'.join([ 'H 0', 'CNOT 0 1', 'DECLARE ro BIT[2]', 'MEASURE 0 ro[0]', 'MEASURE 1 ro[1]', '', ])
def ansatz(params: Dict[int,float], prog_in: Program = program_initialization )-> Program: """ Create a maximally expressive 2-qubit quantum circuit with minimal amount of parameters (15 rotations) Args: params: A dictionary of 15 real parameters for the 15 rotation gates in the ansatz circuit prog_in: A Program to start creating the circuit with. Returns: A Program representing the ansatz circuit. """ prog_out = prog_in.copy() prog_out += RZ(params[0], 1) prog_out += CNOT(0,1) prog_out += RZ(params[1], 0) prog_out += RX(params[2], 0) prog_out += RZ(params[3], 0) prog_out += RZ(params[4], 1) prog_out += RX(params[5], 1) prog_out += RZ(params[6], 1) prog_out += CNOT(0,1) prog_out += RX(params[7], 0) prog_out += RZ(params[8], 1) prog_out += CNOT(0,1) prog_out += RZ(params[9], 0) prog_out += RX(params[10], 0) prog_out += RZ(params[11], 0) prog_out += RZ(params[12], 1) prog_out += RX(params[13], 1) prog_out += RZ(params[14], 1) return prog_out
def __init__(self, prepare_ansatz: Program, make_memory_map: Callable[[Iterable], dict], hamiltonian: PauliSum, qvm: Union[QuantumComputer, str], scalar_cost_function: bool = True, nshots: int = 1, base_numshots: int = 100, qubit_mapping: Dict[QubitPlaceholder, Union[Qubit, int]] = None, enable_logging: bool = False, hamiltonian_is_diagonal: bool =False): self.scalar = scalar_cost_function self.nshots = nshots self.make_memory_map = make_memory_map if isinstance(qvm, str): qvm = get_qc(qvm) self.qvm = qvm if qubit_mapping is not None: prepare_ansatz = address_qubits(prepare_ansatz, qubit_mapping) ham = address_qubits_hamiltonian(hamiltonian, qubit_mapping) else: ham = hamiltonian if not hamiltonian_is_diagonal: self.hams = commuting_decomposition(ham) else: self.hams = [ham] self.exes = [] for ham in self.hams: # need a different program for each of the self commuting hams p = prepare_ansatz.copy() append_measure_register(p, qubits=ham.get_qubits(), trials=base_numshots, ham=ham) self.exes.append(qvm.compile(p)) if enable_logging: self.log = []
def run_and_measure(self, program: Program, trials: int) -> Dict[int, np.ndarray]: """ Run the provided state preparation program and measure all qubits. This will measure all the qubits on this QuantumComputer, not just qubits that are used in the program. 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 sorted(qc.qubits())).T bitstring_array.shape # (trials, len(qc.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 dictionary keyed by qubit index where the corresponding value is a 1D array of measured bits. """ program = program.copy() program = _validate_run_and_measure_program(program) ro = program.declare('ro', 'BIT', len(self.qubits())) for i, q in enumerate(self.qubits()): 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(self.qubits()): bitstring_dict[q] = bitstring_array[:, i] return bitstring_dict