def collect_heavy_outputs(wfn_sim: NumpyWavefunctionSimulator, permutations: np.ndarray, gates: np.ndarray) -> List[int]: """ Collects and returns those 'heavy' bitstrings which are output with greater than median probability among all possible bitstrings on the given qubits. The method uses the provided wfn_sim to calculate the probability of measuring each bitstring from the output of the circuit comprised of the given permutations and gates. :param wfn_sim: a NumpyWavefunctionSimulator that can simulate the provided program :param permutations: array of depth-many arrays of size n_qubits indicating a qubit permutation :param gates: depth by num_gates_per_layer many matrix representations of 2q gates. The first row of matrices is the earliest-time layer of 2q gates applied. :return: a list of the heavy outputs of the circuit, represented as ints """ wfn_sim.reset() for layer_idx, (perm, layer) in enumerate(zip(permutations, gates)): for gate_idx, gate in enumerate(layer): wfn_sim.do_gate_matrix(gate, (perm[gate_idx], perm[gate_idx + 1])) # Note that probabilities are ordered lexicographically with qubit 0 leftmost. probabilities = np.abs(wfn_sim.wf.reshape(-1))**2 median_prob = median(probabilities) # store the integer indices, which implicitly represent the bitstring outcome. heavy_outputs = [ idx for idx, prob in enumerate(probabilities) if prob > median_prob ] return heavy_outputs
def test_expectation_vs_ref_qvm(qvm, n_qubits): for repeat_i in range(20): prog = _generate_random_program(n_qubits=n_qubits, length=10) operator = _generate_random_pauli(n_qubits=n_qubits, n_terms=5) print(prog) print(operator) ref_wf = ReferenceWavefunctionSimulator(n_qubits=n_qubits).do_program(prog) ref_exp = ref_wf.expectation(operator=operator) np_wf = NumpyWavefunctionSimulator(n_qubits=n_qubits).do_program(prog) np_exp = np_wf.expectation(operator=operator) np.testing.assert_allclose(ref_exp, np_exp, atol=1e-15)
def test_exhaustive_state_dfe_run(benchmarker: BenchmarkConnection): wfnsim = NumpyWavefunctionSimulator(n_qubits=1) process = Program(X(0)) texpt = generate_exhaustive_state_dfe_experiment(program=process, qubits=[0], benchmarker=benchmarker) for setting in texpt: setting = setting[0] prog = Program() for oneq_state in setting.in_state.states: prog += _one_q_state_prep(oneq_state) prog += process expectation = wfnsim.reset().do_program(prog).expectation( setting.out_operator) assert expectation == 1.
def test_qv_get_results_by_depth(qvm): depths = [2, 3] n_ckts = 10 n_shots = 5 ckt_results = [] ckt_hhs = [] for depth in depths: wfn_sim = NumpyWavefunctionSimulator(depth) for _ in range(n_ckts): permutations, gates = generate_abstract_qv_circuit(depth) program = _naive_program_generator(qvm, qvm.qubits(), permutations, gates) program.wrap_in_numshots_loop(n_shots) executable = qvm.compiler.native_quil_to_executable(program) results = qvm.run(executable) ckt_results.append(results) heavy_outputs = collect_heavy_outputs(wfn_sim, permutations, gates) ckt_hhs.append(heavy_outputs) num_hh_sampled = count_heavy_hitters_sampled(ckt_results, ckt_hhs) probs_by_depth = get_prob_sample_heavy_by_depth(depths, num_hh_sampled, [n_shots for _ in depths]) assert len(probs_by_depth.keys()) == len(depths) assert [0 <= probs_by_depth[d][1] <= probs_by_depth[d][0] <= 1 for d in depths]
def wfn_measure_observables(n_qubits, tomo_expt: TomographyExperiment): if len(tomo_expt.program.defined_gates) > 0: raise pytest.skip("Can't do wfn on defined gates yet") wfn = NumpyWavefunctionSimulator(n_qubits) for settings in tomo_expt: for setting in settings: prog = Program() for oneq_state in setting.in_state.states: prog += _one_q_state_prep(oneq_state) prog += tomo_expt.program yield ExperimentResult( setting=setting, expectation=wfn.reset().do_program(prog).expectation(setting.out_operator), stddev=0., total_counts=1, # don't set to zero unless you want nans )
def test_monte_carlo_process_dfe(benchmarker: BenchmarkConnection): process = Program(CNOT(0, 1)) texpt = generate_monte_carlo_process_dfe_experiment( program=process, qubits=[0, 1], n_terms=10, benchmarker=benchmarker) assert len(texpt) == 10 wfnsim = NumpyWavefunctionSimulator(n_qubits=2) for setting in texpt: setting = setting[0] prog = Program() for oneq_state in setting.in_state.states: prog += _one_q_state_prep(oneq_state) prog += process expectation = wfnsim.reset().do_program(prog).expectation( setting.out_operator) assert_almost_equal(expectation, 1., decimal=7)
def run(depth, circuit): wfn_sim = NumpyWavefunctionSimulator(depth) start = time.time() heavy_outputs = collect_heavy_outputs(wfn_sim, *circuit) end = time.time() return heavy_outputs, end - start
def sample_rand_circuits_for_heavy_out( qc: QuantumComputer, qubits: Sequence[int], depth: int, program_generator: Callable[ [QuantumComputer, Sequence[int], Sequence[np.ndarray], np.ndarray], Program], num_circuits: int = 100, num_shots: int = 1000, show_progress_bar: bool = False) -> int: """ This method performs the bulk of the work in the quantum volume measurement. For the given depth, num_circuits many random model circuits are generated, the heavy outputs are determined from the ideal output distribution of each circuit, and a native quil implementation of the model circuit output by the program generator is run on the qc. The total number of sampled heavy outputs is returned. :param qc: the quantum resource that will implement the PyQuil program for each model circuit :param qubits: the qubits available in the qc for the program_generator to use. :param depth: the depth (and width in num of qubits) of the model circuits :param program_generator: a method which takes an abstract description of a model circuit and returns a native quil program that implements that circuit. See measure_quantum_volume docstring for specifics. :param num_circuits: the number of random model circuits to sample at this depth; should be >100 :param num_shots: the number of shots to sample from each model circuit :param show_progress_bar: displays a progress bar via tqdm if true. :return: the number of heavy outputs sampled among all circuits generated for this depth """ wfn_sim = NumpyWavefunctionSimulator(depth) num_heavy = 0 # display progress bar using tqdm for _ in tqdm(range(num_circuits), disable=not show_progress_bar): permutations, gates = generate_abstract_qv_circuit(depth) # generate a PyQuil program in native quil that implements the model circuit # The program should measure the output qubits in the order that is consistent with the # comparison of the bitstring results to the heavy outputs given by collect_heavy_outputs program = program_generator(qc, qubits, permutations, gates) # run the program num_shots many times program.wrap_in_numshots_loop(num_shots) executable = qc.compiler.native_quil_to_executable(program) results = qc.run(executable) # classically simulate model circuit represented by the perms and gates for heavy outputs heavy_outputs = collect_heavy_outputs(wfn_sim, permutations, gates) # determine if each result bitstring is a heavy output, as determined from simulation for result in results: # convert result to int for comparison with heavy outputs. output = bit_array_to_int(result) if output in heavy_outputs: num_heavy += 1 return num_heavy
def single_q_tomo_fixture(): qubits = [0] qc = get_test_qc(n_qubits=len(qubits)) # Generate random unitary u_rand = haar_rand_unitary(2 ** 1, rs=np.random.RandomState(52)) state_prep = Program().defgate("RandUnitary", u_rand) state_prep.inst([("RandUnitary", qubits[0])]) # True state wfn = NumpyWavefunctionSimulator(n_qubits=1) psi = wfn.do_gate_matrix(u_rand, qubits=[0]).wf.reshape(-1) rho_true = np.outer(psi, psi.T.conj()) # Get data from QVM tomo_expt = generate_state_tomography_experiment(state_prep, qubits) results = list(measure_observables(qc=qc, tomo_experiment=tomo_expt, n_shots=4000)) return results, rho_true
def two_q_tomo_fixture(test_qc): qubits = [0, 1] # Generate random unitary u_rand1 = haar_rand_unitary(2 ** 1, rs=np.random.RandomState(52)) u_rand2 = haar_rand_unitary(2 ** 1, rs=np.random.RandomState(53)) state_prep = Program().defgate("RandUnitary1", u_rand1).defgate("RandUnitary2", u_rand2) state_prep.inst(("RandUnitary1", qubits[0])).inst(("RandUnitary2", qubits[1])) # True state wfn = NumpyWavefunctionSimulator(n_qubits=2) psi = wfn \ .do_gate_matrix(u_rand1, qubits=[0]) \ .do_gate_matrix(u_rand2, qubits=[1]) \ .wf.reshape(-1) rho_true = np.outer(psi, psi.T.conj()) # Get data from QVM tomo_expt = generate_state_tomography_experiment(state_prep, qubits) results = list(estimate_observables(qc=test_qc, obs_expt=tomo_expt, num_shots=1000, symm_type=-1)) results = list(calibrate_observable_estimates(test_qc, results)) return results, rho_true
def test_expectation(): wfn = NumpyWavefunctionSimulator(n_qubits=3) val = wfn.expectation(0.4 * sZ(0) + sX(2)) assert val == 0.4
def objective_function(self, amps=None): """ This function returns the Hamiltonian expectation value over the final circuit output state. If argument packed_amps is given, the circuit will run with those parameters. Otherwise, the initial angles will be used. :param [list(), numpy.ndarray] amps: list of circuit angles to run the objective function over. :return: energy estimate :rtype: float """ E = 0 t = time.time() if amps is None: packed_amps = self.initial_packed_amps elif isinstance(amps, np.ndarray): packed_amps = amps.tolist()[:] elif isinstance(amps, list): packed_amps = amps[:] else: raise TypeError('Please supply the circuit parameters as a list or np.ndarray') if self.tomography: if (not self.parametric_way) and (self.strategy == 'UCCSD'): # modify hard-coded type ansatz circuit based # on packed_amps angles self.ansatz = uccsd_ansatz_circuit(packed_amps, self.molecule.n_orbitals, self.molecule.n_electrons, cq=self.custom_qubits) self.compile_tomo_expts() for experiment in self.experiment_list: E += experiment.run_experiment(self.qc, packed_amps) # Run tomography experiments E += self.offset # add the offset energy to avoid doing superfluous tomography over the identity operator. elif self.method == 'WFS': # In the direct WFS method without tomography, direct access to wavefunction is allowed and expectation # value is exact each run. if self.parametric_way: E += WavefunctionSimulator().expectation(self.ref_state+self.ansatz, self.pauli_sum, {'theta': packed_amps}).real # attach parametric angles here else: if packed_amps is not None: # modify hard-coded type ansatz circuit based on packed_amps angles self.ansatz = uccsd_ansatz_circuit(packed_amps, self.molecule.n_orbitals, self.molecule.n_electrons, cq=self.custom_qubits) E += WavefunctionSimulator().expectation(self.ref_state+self.ansatz, self.pauli_sum).real elif self.method == 'Numpy': if self.parametric_way: raise ValueError('NumpyWavefunctionSimulator() backend does not yet support parametric programs.') else: if packed_amps is not None: self.ansatz = uccsd_ansatz_circuit(packed_amps, self.molecule.n_orbitals, self.molecule.n_electrons, cq=self.custom_qubits) E += NumpyWavefunctionSimulator(n_qubits=self.n_qubits).\ do_program(self.ref_state+self.ansatz).expectation(self.pauli_sum).real elif self.method == 'linalg': # check if molecule has data sufficient to construct UCCSD ansatz and propagate starting from HF state if self.molecule is not None: propagator = normal_ordered(uccsd_singlet_generator(packed_amps, 2 * self.molecule.n_orbitals, self.molecule.n_electrons, anti_hermitian=True)) qubit_propagator_matrix = get_sparse_operator(propagator, n_qubits=self.n_qubits) uccsd_state = expm_multiply(qubit_propagator_matrix, self.initial_psi) expected_uccsd_energy = expectation(self.hamiltonian_matrix, uccsd_state).real E += expected_uccsd_energy else: # apparently no molecule was supplied; attempt to just propagate the ansatz from user-specified # initial state, using a circuit unitary if supplied by the user, otherwise the initial state itself, # and then estimate over <H> if self.initial_psi is None: raise ValueError('Warning: no initial wavefunction set. Please set using ' 'VQEexperiment().set_initial_state()') # attempt to propagate with a circuit unitary if self.circuit_unitary is None: psi = self.initial_psi else: psi = expm_multiply(self.circuit_unitary, self.initial_psi) E += expectation(self.hamiltonian_matrix, psi).real else: raise ValueError('Impossible method: please choose from method = {WFS, Numpy, linalg} if Tomography is set' ' to False, or choose from method = {QC, WFS, Numpy, linalg} if tomography is set to True') if self.verbose: self.it_num += 1 print('black-box function call #' + str(self.it_num)) print('Energy estimate is now: ' + str(E)) print('at angles: ', packed_amps) print('and this took ' + '{0:.3f}'.format(time.time()-t) + ' seconds to evaluate') self.history.append(E) return E