def run(self, qc: QuantumComputer, bitstring_map: Dict[str, str]) -> 'BernsteinVazirani': """ Runs the Bernstein-Vazirani algorithm. Given a connection to a QVM or QPU, find the :math:`\\mathbf{a}` and :math:`b` corresponding to the function represented by the oracle function that will be constructed from the bitstring map. :param qc: connection to the QPU or QVM :param bitstring_map: a truth table describing the boolean function, whose dot-product vector and bias is to be found """ # initialize all attributes self.input_bitmap = bitstring_map self.n_qubits = len(list(bitstring_map.keys())[0]) self.computational_qubits = list(range(self.n_qubits)) self.ancilla = self.n_qubits # is the highest index now. # construct BV circuit self.bv_circuit = self._create_bv_circuit(bitstring_map) # find vector by running the full bv circuit full_circuit = Program() full_ro = full_circuit.declare('ro', 'BIT', len(self.computational_qubits) + 1) full_circuit += self.bv_circuit full_circuit += [ MEASURE(qubit, ro) for qubit, ro in zip(self.computational_qubits, full_ro) ] full_executable = qc.compile(full_circuit) full_results = qc.run(full_executable) bv_vector = full_results[0][::-1] # To get the bias term we skip the Walsh-Hadamard transform ancilla_circuit = Program() ancilla_ro = ancilla_circuit.declare( 'ro', 'BIT', len(self.computational_qubits) + 1) ancilla_circuit += self.bv_circuit ancilla_circuit += [MEASURE(self.ancilla, ancilla_ro[self.ancilla])] ancilla_executable = qc.compile(ancilla_circuit) ancilla_results = qc.run(ancilla_executable) bv_bias = ancilla_results[0][0] self.solution = ''.join([str(b) for b in bv_vector]), str(bv_bias) return self
def get_parametric_program(qc: QuantumComputer, q0: int, q1: int, n_shots: int = 1000): """ Run one (beta, gamma) point for the simplest Maxcut QAOA. :param qc: The QuantumComputer to use :param beta: The beta angle (p = 1) :param gamma: The gamma angle (p = 1) :param q0: The index of the first qubit :param q1: The index of the second qubit :param n_shots: The number of shots to take for this (beta, gamma) point. """ ising = generate_maxcut_ising(nx.from_edgelist([(0, 1)])) driver = sX(q0) + sX(q1) reward = sZ(q0) * sZ(q1) + 0 # add 0 to turn to PauliSum program = Program() beta = program.declare('beta', memory_type='REAL') gamma = program.declare('gamma', memory_type='REAL') program += get_qaoa_program(qubits=[q0, q1], driver=driver, reward=reward, betas=[beta], gammas=[gamma]) ro = program.declare('ro', memory_size=2) program += MEASURE(q0, ro[0]) program += MEASURE(q1, ro[1]) program = program.wrap_in_numshots_loop(shots=n_shots) executable = qc.compile(program) return executable, ising
def find_bitstring(self, qc: QuantumComputer, bitstring_map: Dict[str, int]) -> str: """ Runs Grover's Algorithm to find the bitstring that is designated by ``bistring_map``. In particular, this will prepare an initial state in the uniform superposition over all bit- strings, an then use Grover's Algorithm to pick out the desired bitstring. :param qc: the connection to the Rigetti cloud to run pyQuil programs. :param bitstring_map: a mapping from bitstrings to the phases that the oracle should impart on them. If the oracle should "look" for a bitstring, it should have a ``-1``, otherwise it should have a ``1``. :return: Returns the bitstring resulting from measurement after Grover's Algorithm. """ self._init_attr(bitstring_map) ro = self.grover_circuit.declare('ro', 'BIT', len(self.qubits)) self.grover_circuit += [ MEASURE(qubit, ro[idx]) for idx, qubit in enumerate(self.qubits) ] executable = qc.compile(self.grover_circuit) sampled_bitstring = qc.run(executable) return "".join([str(bit) for bit in sampled_bitstring[0]])
def test_qpu_run(): config = PyquilConfig() if config.qpu_url and config.qpu_compiler_url: g = nx.Graph() g.add_node(0) device = NxDevice(g) qc = QuantumComputer( name="pyQuil test QC", qam=QPU(endpoint=config.qpu_url, user="******"), device=device, compiler=QPUCompiler( quilc_endpoint=config.quilc_url, qpu_compiler_endpoint=config.qpu_compiler_url, device=device, ), ) bitstrings = qc.run_and_measure(program=Program(X(0)), trials=1000) assert bitstrings[0].shape == (1000, ) assert np.mean(bitstrings[0]) > 0.8 bitstrings = qc.run(qc.compile(Program(X(0)))) assert bitstrings.shape == (0, 0) else: pytest.skip( "QPU or compiler-server not available; skipping QPU run test.")
def with_quilc_parametric_compilation( *, quantum_computer: QuantumComputer, circuit: cirq.Circuit, resolvers: Sequence[cirq.ParamResolverOrSimilarType], repetitions: int, transformer: transformers.CircuitTransformer = transformers.default, ) -> List[cirq.Result]: """This `CircuitSweepExecutor` will compile the `circuit` using quilc as a parameterized `pyquil.api.QuantumExecutable` and on each iteration of `resolvers`, rather than resolving the `circuit` with `cirq.protocols.resolve_parameters`, it will attempt to cast the resolver to a dict and pass it as a memory map to to `pyquil.api.QuantumComputer`. Args: quantum_computer: The `pyquil.api.QuantumComputer` against which to execute the circuit. circuit: The `cirq.Circuit` to transform into a `pyquil.Program` and executed on the `quantum_computer`. resolvers: A sequence of parameter resolvers that this executor will write to memory on a copy of the `pyquil.api.QuantumExecutable` for each parameter sweep. repetitions: Number of times to run each iteration through the `resolvers`. For a given resolver, the `cirq.Result` will include a measurement for each repetition. transformer: A callable that transforms the `cirq.Circuit` into a `pyquil.Program`. You may pass your own callable or any function from `cirq_rigetti.circuit_transformers`. Returns: A list of `cirq.Result`, each corresponding to a resolver in `resolvers`. """ program, measurement_id_map = transformer(circuit=circuit) program = _prepend_real_declarations(program=program, resolvers=resolvers) program.wrap_in_numshots_loop(repetitions) executable = quantum_computer.compile(program) cirq_results = [] for resolver in resolvers: memory_map = _get_param_dict(resolver) logger.debug( f"running pre-compiled parametric circuit with parameters {memory_map}" ) result = _execute_and_read_result( quantum_computer, executable.copy(), measurement_id_map, resolver, memory_map=memory_map, ) cirq_results.append(result) return cirq_results
def estimate_confusion_matrix(qc: QuantumComputer, qubit: int, num_shots: int = 10000) \ -> np.ndarray: """ Estimate the readout confusion matrix for a given qubit. The confusion matrix will be of the form:: [[ p(0 | 0) p(0 | 1) ] [ p(1 | 0) p(1 | 1) ]] where each column sums to one. Note that the matrix need not be symmetric. :param qc: The quantum computer to estimate the confusion matrix. :param qubit: The actual physical qubit to measure :param num_shots: The number of shots to take. This function runs two programs, so the total number of shots taken will be twice this number. :return: a 2x2 confusion matrix for the qubit, where each row sums to one. """ # prepare 0 (do nothing) and measure; repeat shots number of times zero_meas = Program() ro_zero = zero_meas.declare("ro", "BIT", 1) zero_meas += MEASURE(qubit, ro_zero[0]) zero_meas.wrap_in_numshots_loop(num_shots) should_be_0 = qc.run(qc.compile(zero_meas)) # prepare one and measure; repeat shots number of times one_meas = Program() one_meas += RX(pi, qubit) ro_one = one_meas.declare("ro", "BIT", 1) one_meas += MEASURE(qubit, ro_one[0]) one_meas.wrap_in_numshots_loop(num_shots) should_be_1 = qc.run(qc.compile(one_meas)) p00 = 1 - np.mean(should_be_0) p11 = np.mean(should_be_1) return np.array([[p00, 1 - p00], [1 - p11, p11]])
def expectation_from_sampling(pyquil_program: Program, marked_qubits: List[int], qc: QuantumComputer, samples: int, stacked_params: np.ndarray) -> float: """ Calculation of Z_{i} at marked_qubits Given a wavefunctions, this calculates the expectation value of the Zi operator where i ranges over all the qubits given in marked_qubits. :param pyquil_program: pyQuil program generating some state :param marked_qubits: The qubits within the support of the Z pauli operator whose expectation value is being calculated :param qc: A QuantumComputer object. :param samples: Number of bitstrings collected to calculate expectation from sampling. :returns: The expectation value as a float. """ program = Program() ro = program.declare('ro', 'BIT', max(marked_qubits) + 1) program += pyquil_program program += [ MEASURE(qubit, r) for qubit, r in zip(list(range(max(marked_qubits) + 1)), ro) ] program.wrap_in_numshots_loop(samples) try: executable = qc.compile(program) except: import pdb pdb.set_trace() bitstring_samples = qc.run(executable) bitstring_tuples = list(map(tuple, bitstring_samples)) freq = Counter(bitstring_tuples) # perform weighted average expectation = 0 for bitstring, count in freq.items(): bitstring_int = int("".join([str(x) for x in bitstring[::-1]]), 2) if parity_even_p(bitstring_int, marked_qubits): expectation += float(count) / samples else: expectation -= float(count) / samples return expectation
def with_quilc_compilation_and_cirq_parameter_resolution( *, quantum_computer: QuantumComputer, circuit: cirq.Circuit, resolvers: Sequence[cirq.ParamResolverOrSimilarType], repetitions: int, transformer: transformers.CircuitTransformer = transformers.default, ) -> Sequence[cirq.Result]: """This `CircuitSweepExecutor` will first resolve each resolver in `resolvers` using `cirq.protocols.resolve_parameters` and then compile that resolved `cirq.Circuit` into native Quil using quilc. This executor may be useful if `with_quilc_parametric_compilation` fails to properly resolve a parameterized `cirq.Circuit`. Args: quantum_computer: The `pyquil.api.QuantumComputer` against which to execute the circuit. circuit: The `cirq.Circuit` to transform into a `pyquil.Program` and executed on the `quantum_computer`. resolvers: A sequence of parameter resolvers that `cirq.protocols.resolve_parameters` will use to fully resolve the circuit. repetitions: Number of times to run each iteration through the `resolvers`. For a given resolver, the `cirq.Result` will include a measurement for each repetition. transformer: A callable that transforms the `cirq.Circuit` into a `pyquil.Program`. You may pass your own callable or any function from `cirq_rigetti.circuit_transformers`. Returns: A list of `cirq.Result`, each corresponding to a resolver in `resolvers`. """ cirq_results = [] for resolver in resolvers: resolved_circuit = cirq.protocols.resolve_parameters(circuit, resolver) program, measurement_id_map = transformer(circuit=resolved_circuit) program.wrap_in_numshots_loop(repetitions) executable = quantum_computer.compile(program) result = _execute_and_read_result( quantum_computer, executable, measurement_id_map, resolver ) cirq_results.append(result) return cirq_results
def run_point(qc: QuantumComputer, beta: float, gamma: float, q0: int, q1: int, n_shots: int = 1000): """ Run one (beta, gamma) point for the simplest Maxcut QAOA. :param qc: The QuantumComputer to use :param beta: The beta angle (p = 1) :param gamma: The gamma angle (p = 1) :param q0: The index of the first qubit :param q1: The index of the second qubit :param n_shots: The number of shots to take for this (beta, gamma) point. """ # Construct a program ising = generate_maxcut_ising(nx.from_edgelist([(0, 1)])) driver = sX(q0) + sX(q1) reward = sZ(q0) * sZ(q1) + 0 # add 0 to turn to PauliSum program = get_qaoa_program(qubits=[q0, q1], driver=driver, reward=reward, betas=[beta], gammas=[gamma]) ro = program.declare('ro', memory_size=2) program += MEASURE(q0, ro[0]) program += MEASURE(q1, ro[1]) program = program.wrap_in_numshots_loop(shots=n_shots) # Compile it executable = qc.compile(program) # Run and post-process bitstrings = qc.run(executable) rewards = calculate_ising_rewards(ising, bitstrings) return np.mean(rewards)
def test_set_initial_state(client_configuration: QCSClientConfiguration): # That is test the assigned state matrix in ReferenceDensitySimulator is persistent between # rounds of run. rho1 = np.array([[0.0, 0.0], [0.0, 1.0]]) # run prog prog = Program(I(0)) ro = prog.declare("ro", "BIT", 1) prog += MEASURE(0, ro[0]) # make a quantum computer object device = NxQuantumProcessor(nx.complete_graph(1)) qc_density = QuantumComputer( name="testy!", qam=PyQVM(n_qubits=1, quantum_simulator_type=ReferenceDensitySimulator), compiler=DummyCompiler(quantum_processor=device, client_configuration=client_configuration), ) qc_density.qam.wf_simulator.set_initial_state(rho1).reset() out = [qc_density.run(prog).readout_data['ro'] for _ in range(0, 4)] ans = [np.array([[1]]), np.array([[1]]), np.array([[1]]), np.array([[1]])] assert all([np.allclose(x, y) for x, y in zip(out, ans)]) # Run and measure style progRAM = prog.copy().wrap_in_numshots_loop(10) results = qc_density.run(qc_density.compile(progRAM)) ans = {0: np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])} assert np.allclose(results.readout_data['ro'][0], ans[0]) # test reverting ReferenceDensitySimulator to the default state rho0 = np.array([[1.0, 0.0], [0.0, 0.0]]) qc_density.qam.wf_simulator.set_initial_state(rho0).reset() assert np.allclose(qc_density.qam.wf_simulator.density, rho0) assert np.allclose(qc_density.qam.wf_simulator.initial_density, rho0)
def is_constant(self, qc: QuantumComputer, bitstring_map: Dict[str, str]) -> bool: """Computes whether bitstring_map represents a constant function, given that it is constant or balanced. Constant means all inputs map to the same value, balanced means half of the inputs maps to one value, and half to the other. :param QVMConnection cxn: The connection object to the Rigetti cloud to run pyQuil programs. :param bitstring_map: A dictionary whose keys are bitstrings, and whose values are bits represented as strings. :type bistring_map: Dict[String, String] :return: True if the bitstring_map represented a constant function, false otherwise. :rtype: bool """ self._init_attr(bitstring_map) prog = Program() dj_ro = prog.declare('ro', 'BIT', len(self.computational_qubits)) prog += self.deutsch_jozsa_circuit prog += [MEASURE(qubit, ro) for qubit, ro in zip(self.computational_qubits, dj_ro)] executable = qc.compile(prog) returned_bitstring = qc.run(executable) # We are only running a single shot, so we are only interested in the first element. bitstring = np.array(returned_bitstring, dtype=int) constant = all([bit == 0 for bit in bitstring]) return constant
def _sample_independent_bit_vectors(self, qc: QuantumComputer) -> None: """ This method samples :math:`n-1` linearly independent vectors using the Simon Circuit. It attempts to put the sampled bitstring into a dictionary and only terminates once the dictionary contains :math:`n-1` samples :param qc: Connection object to a QuantumComputer. :return: None """ while len( self._dict_of_linearly_indep_bit_vectors) < self.n_qubits - 1: prog = Program() simon_ro = prog.declare('ro', 'BIT', len(self.computational_qubits)) prog += self.simon_circuit prog += [ MEASURE(qubit, ro) for qubit, ro in zip(self.computational_qubits, simon_ro) ] executable = qc.compile(prog) sampled_bit_string = np.array(qc.run(executable)[0], dtype=int) self._add_to_dict_of_indep_bit_vectors(sampled_bit_string)
def test_parametric_program(qc: QuantumComputer): compiled = qc.compile( Program( Declare("ro", "BIT", 1), Declare("theta", "REAL", 1), RX(MemoryReference("theta"), 0), MEASURE(0, ("ro", 0)), ).wrap_in_numshots_loop(1000), ) all_results = [] for theta in [0, np.pi, 2 * np.pi]: compiled.write_memory(region_name="theta", value=theta) results = qc.run(compiled).readout_data.get("ro") all_results.append(np.mean(results)) if isinstance(qc.qam, QPU): assert all_results[0] < 0.2 assert all_results[1] > 0.8 assert all_results[2] < 0.2 else: assert all_results[0] == 0.0 assert all_results[1] > 0.8 assert all_results[2] == 0.0
def run_program( program: Program, qc: QuantumComputer, ) -> np.ndarray: return qc.run(qc.compile(program)).readout_data.get('ro')
def test_basic_program(qc: QuantumComputer): results = qc.run(qc.compile(TEST_PROGRAM)).readout_data.get("ro") assert results.shape == (1000, 2)
def __init__( self, list_gsuit_paulis: List[PauliTerm], qc: QuantumComputer, ref_state: Program, ansatz: Program, shotN: int, parametric_way: bool, n_qubits: int, active_reset: bool = True, cq=None, method='QC', verbose: bool = False, calibration: bool = True, ): """ A tomography experiment class for use in VQE. In a real experiment, one only has access to measurements in the Z-basis, giving a result 0 or 1 for each qubit. The Hamiltonian may have terms like sigma_x or sigma_y, for which a basis rotation is required. As quantum mechanics prohibits measurements in multiple bases at once, the Hamiltonian needs to be grouped into commuting Pauli terms and for each set of terms the appropriate basis rotations is applied to each qubits. A parity_matrix is constructed to keep track of what consequence a qubit measurement of 0 or 1 has as a contribution to the Pauli estimation. The experiments are constructed as objects with class methods to run and adjust them. Instantiate using the following parameters: :param list() list_gsuit_paulis: list of Pauli terms which can be measured at the same time (they share a TPB!) :param QuantumComputer qc: QuantumComputer() object which will simulate the terms :param Program ref_state: Program() circuit object which produces the initial reference state (f.ex. Hartree-Fock) :param Program ansatz: Program() circuit object which produces the ansatz (f.ex. UCCSD) :param int shotN: number of shots to run this Setting for :param bool parametric_way: boolean whether to use parametric gates or hard-coded gates :param int n_qubits: total number of qubits used for the program :param bool active_reset: boolean whether or not to actively reset the qubits :param list() cq: list of qubit labels instead of the default [0,1,2,3,...,N-1] :param str method: string describing the computational method from {QC, linalg, WFS, Numpy} :param bool verbose: boolean, whether or not to give verbose output during execution """ self.parametric_way = parametric_way self.pauli_list = list_gsuit_paulis self.shotN = shotN self.method = method self.parity_matrix = self.construct_parity_matrix( list_gsuit_paulis, n_qubits) self.verbose = verbose self.n_qubits = n_qubits self.cq = cq self.calibration = calibration if qc is not None: if qc.name[-4:] == 'yqvm' and self.cq is not None: raise NotImplementedError( 'manual qubit lattice feed' ' for PyQVM not yet implemented. please set cq=None') else: if self.method == 'QC': raise ValueError('method is QC but no QuantumComputer' ' object supplied.') # instantiate a new program and construct it for compilation prog = Program() if self.method == 'QC': ro = prog.declare('ro', memory_type='BIT', memory_size=self.n_qubits) if active_reset and self.method == 'QC': if not qc.name[-4:] == 'yqvm': # in case of PyQVM, can not contain reset statement prog += RESET() # circuit which produces reference state (f.ex. Hartree-Fock) prog += ref_state # produce which prepares an ansatz state starting # from a reference state (f.ex. UCCSD or swap network UCCSD) prog += ansatz self.term_keys = [] already_done = [] for pauli in list_gsuit_paulis: # save the id for each term self.term_keys.append(pauli.operations_as_set()) # also, we perform the necessary rotations # going from X or Y to Z basis for (i, st) in pauli.operations_as_set(): if (st == 'X' or st == 'Y') and i not in already_done: # note that 'i' is the *logical* index # corresponding to the pauli. if cq is not None: # if the logical qubit should be remapped # to physical qubits, access this cq prog += pauli_meas(cq[i], st) else: prog += pauli_meas(i, st) # if we already have rotated the basis # due to another term, don't do it again! already_done.append(i) if self.method != 'QC': self.pure_pyquil_program = Program(prog) else: # measure the qubits and assign the result # to classical register ro for i in range(self.n_qubits): if cq is not None: prog += MEASURE(cq[i], ro[i]) else: prog += MEASURE(i, ro[i]) prog2 = percolate_declares(prog) # wrap in shotN number of executions on the qc, # to get operator measurement by sampling prog2.wrap_in_numshots_loop(shots=self.shotN) # print(self.term_keys) # print circuits: # from pyquil.latex import to_latex # print(to_latex(prog2)) # input() if qc.name[-4:] == 'yqvm': self.pyqvm_program = prog2 else: self.pyquil_executable = qc.compile(prog2) # print("compiled") # print(to_latex(qc.compiler.quil_to_native_quil(prog2))) # input() # now about calibration if self.calibration and self.method != 'QC': # turn off calibration if not QC. self.calibration = False if self.calibration: # prepare and run the calibration experiments prog = Program() ro = prog.declare('ro', memory_type='BIT', memory_size=self.n_qubits) if active_reset: if not qc.name[-4:] == 'yqvm': # in case of PyQVM, can not contain reset statement prog += RESET() # circuit which produces reference state, # which is in the case of calibration experiments # the same as the out_operators. already_done = [] for pauli in list_gsuit_paulis: # also, we perform the necessary rotations # going to X/Y/Z =1 state for (i, st) in pauli.operations_as_set(): if (st == 'X' or st == 'Y') and i not in already_done: # note that 'i' is the *logical* index # corresponding to the pauli. if cq is not None: # if the logical qubit should be remapped # to physical qubits, access this cq prog += pauli_meas(cq[i], st).dagger() else: prog += pauli_meas(i, st).dagger() # if we already have rotated the basis # due to another term, don't do it again! already_done.append(i) # measurement now already_done = [] for pauli in list_gsuit_paulis: # also, we perform the necessary rotations # going from X or Y to Z basis for (i, st) in pauli.operations_as_set(): if (st == 'X' or st == 'Y') and i not in already_done: # note that 'i' is the *logical* index # corresponding to the pauli. if cq is not None: # if the logical qubit should be remapped # to physical qubits, access this cq prog += pauli_meas(cq[i], st) else: prog += pauli_meas(i, st) # if we already have rotated the basis # due to another term, don't do it again! already_done.append(i) # measure the qubits and assign the result # to classical register ro for i in range(self.n_qubits): if cq is not None: prog += MEASURE(cq[i], ro[i]) else: prog += MEASURE(i, ro[i]) prog2 = percolate_declares(prog) # wrap in shotN number of executions on the qc, # to get operator measurement by sampling prog2.wrap_in_numshots_loop(shots=self.shotN) if qc.name[-4:] == 'yqvm': self.pyqvm_program = prog2 bitstrings = qc.run(self.pyqvm_program) # compile to native quil if it's not a PYQVM else: pyquil_executable = qc.compile(prog2) bitstrings = qc.run(pyquil_executable) # start data processing # this matrix computes the pauli string parity, # and stores that for each bitstring is_odd = np.mod(bitstrings.dot(self.parity_matrix), 2) # if the parity is odd, the bitstring gives a -1 eigenvalue, # and +1 vice versa. # sum over all bitstrings, average over shotN shots, # and weigh each pauli string by its coefficient e_array = 1 - 2 * np.sum(is_odd, axis=0) / self.shotN self.calibration_norms = e_array print(f"calibration_norms: {e_array}")