Beispiel #1
0
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
Beispiel #2
0
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)
Beispiel #7
0
    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
Beispiel #8
0
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
Beispiel #9
0
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
Beispiel #12
0
    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