Пример #1
0
def estimate_joint_confusion_in_set(qc: QuantumComputer, qubits: Sequence[int] = None,
                                    num_shots: int = 1000, joint_group_size: int = 1,
                                    use_param_program: bool = True, use_active_reset=False,
                                    show_progress_bar: bool = False) \
                                    -> Dict[Tuple[int, ...], np.ndarray]:
    """
    Measures the joint readout confusion matrix for all groups of size group_size among the qubits.

    Simultaneous readout of multiple qubits may exhibit correlations in readout error.
    Measuring a single qubit confusion for each qubit of interest is therefore not sufficient to
    completely characterize readout error in general. This method estimates joint readout confusion
    matrices for groups of qubits, which involves preparing all 2^joint_group_size possible
    bitstrings on each of (len(qubits) choose joint_group_size) many groups. The joint_group_size
    specifies which order of correlation you wish to characterize, and a separate confusion
    matrix is estimated for every possible group of that size.

    For example, a joint_group_size of one will simply estimate a single-qubit 2x2 confusion
    matrix for each of the provided qubits, similar to len(qubits) calls to
    estimate_confusion_matrix. Meanwhile, a joint_group_size of two will yield estimates of the
    (len(qubits) choose 2) many 4x4 confusion matrices for each possible pair of qubits. When the
    joint_group_size is the same as len(qubits) a single confusion matrix is estimated,
    which requires 2^len(qubits) number of state preparations. Note that the maximum number of
    measurements occurs when 1 << joint_group_size << len(qubits), and this maximum is quite large.

    Because this method performs the measurement of exponentially many bitstrings on a particular
    group of qubits, use_param_program should result in an appreciable speed up for estimation
    of each matrix by looping over bitstrings at a lower level of the stack, rather than creating
    a new program for each bitstring. Use_active_reset should also speed up estimation, at the cost
    of introducing more error and potentially complicating interpretation of results.

    :param qc: a quantum computer whose readout error you wish to characterize
    :param qubits: a list of accessible qubits on the qc you wish to characterize. Defaults to
        all qubits in qc.
    :param num_shots: number of shots in measurement of each bit string on each joint group of
        qubits.
    :param joint_group_size: the size of each group; a joint confusion matrix with 
        2^joint_group_size number of rows/columns will be estimated for each group of qubits of 
        the given size within the qubit set. 
    :param use_param_program: dictates whether to use a parameterized program to measure out
        each bitstring. If set to default of True, this routine should execute faster on a
        QPU. Note that the parameterized option does not execute a no-op when measuring 0.
    :param use_active_reset: if true, all qubits in qc (not just provided qubits) will be actively
        reset to 0 state at the start of each bitstring measurement. This option is intended as a
        potential speed up, but may complicate interpretation of the estimated confusion
        matrices. The method estimate_joint_active_reset_confusion separately characterizes
        active reset.
    :param show_progress_bar: displays a progress bar via tqdm if true.
    :return: a dictionary whose keys are all possible joint_group_sized tuples that can be
        formed from the qubits. Each value is an estimated 2^group_size square confusion matrix
        for the corresponding tuple of qubits. Each key is listed in order of increasing qubit
        number. The corresponding matrix has rows and columns indexed in increasing bitstring
        order, with most significant (leftmost) bit labeling the smallest qubit number.
    """
    # establish default as all operational qubits in qc
    if qubits is None:
        qubits = qc.qubits()

    qubits = sorted(qubits)

    groups = list(itertools.combinations(qubits, joint_group_size))
    confusion_matrices = {}

    total_num_rounds = len(groups) * 2**joint_group_size
    with tqdm(total=total_num_rounds, disable=not show_progress_bar) as pbar:
        for group in groups:

            # initialize program to optionally actively reset. Active reset will provide a speed up
            # but may make the estimate harder to interpret due to errors in the reset.
            program_start = Program()
            if use_active_reset:
                program_start += RESET()

            executable = None  # will be re-assigned to either compiled param or non-param program

            # generate one parameterized program now, or later create a program for each bitstring
            if use_param_program:
                # generate parameterized program for this group and append to start
                param_program = program_start + _readout_group_parameterized_bitstring(
                    group)
                param_program.wrap_in_numshots_loop(shots=num_shots)
                executable = qc.compiler.native_quil_to_executable(
                    param_program)

            matrix = np.zeros((2**joint_group_size, 2**joint_group_size))
            for row, bitstring in enumerate(
                    itertools.product([0, 1], repeat=joint_group_size)):

                if use_param_program:
                    # specify bitstring in parameterization at run-time
                    results = qc.run(
                        executable,
                        memory_map={'target': [bit * pi for bit in bitstring]})
                else:
                    # generate program that measures given bitstring on group, and append to start
                    bitstring_program = program_start + _readout_group_bitstring(
                        group, bitstring)
                    bitstring_program.wrap_in_numshots_loop(shots=num_shots)
                    executable = qc.compiler.native_quil_to_executable(
                        bitstring_program)
                    results = qc.run(executable)

                # update confusion matrix
                for result in results:
                    base = np.array(
                        [2**i for i in reversed(range(joint_group_size))])
                    observed = np.sum(base * result)
                    matrix[row, observed] += 1 / num_shots

                # update the progress bar
                pbar.update(1)

            # store completed confusion matrix in dictionary
            confusion_matrices[group] = matrix

    return confusion_matrices
Пример #2
0
def estimate_joint_reset_confusion(qc: QuantumComputer, qubits: Sequence[int] = None,
                                   num_trials: int = 10, joint_group_size: int = 1,
                                   use_active_reset: bool = True, show_progress_bar: bool = False) \
                                    -> Dict[Tuple[int, ...], np.ndarray]:
    """
    Measures a reset 'confusion matrix' for all groups of size joint_group_size among the qubits.

    Specifically, for each possible joint_group_sized group among the qubits we perform a
    measurement for each bitstring on that group. The measurement proceeds as follows:
        -Repeatedly try to prepare the bitstring state on the qubits until success.
        -If use_active_reset is true (default) actively reset qubits to ground state; otherwise,
            wait the preset amount of time for qubits to decay to ground state.
        -Measure the state after the chosen reset method.
    Since reset should result in the all zeros state this 'confusion matrix' should ideally have
    all ones down the left-most column of the matrix. The entry at (row_idx, 0) thus represents
    the success probability of reset given that the pre-reset state is the binary representation
    of the number row_idx. WARNING: this method can be very slow

    :param qc: a quantum computer whose reset error you wish to characterize
    :param qubits: a list of accessible qubits on the qc you wish to characterize. Defaults to
        all qubits in qc.
    :param num_trials: number of repeated trials of reset after preparation of each bitstring on
        each joint group of qubits. Note: num_trials does not correspond to num_shots; a new
        program must be run in each trial, so running a group of n trials takes longer than would
        collecting the same number of shots output from a single program.
    :param joint_group_size: the size of each group; a square matrix with 2^joint_group_size
        number of rows/columns will be estimated for each group of qubits of the given size
        among the provided qubits.
    :param use_active_reset: dictates whether to actively reset qubits or else wait the
        pre-allotted amount of time for the qubits to decay to the ground state. Using active
        reset will allow for faster data collection.
    :param show_progress_bar: displays a progress bar via tqdm if true.
    :return: a dictionary whose keys are all possible joint_group_sized tuples that can be
        formed from the qubits. Each value is an estimated 2^group_size square matrix
        for the corresponding tuple of qubits. Each key is listed in order of increasing qubit
        number. The corresponding matrix has rows and columns indexed in increasing bitstring
        order, with most significant (leftmost) bit labeling the smallest qubit number. Each
        matrix row corresponds to the bitstring that was prepared before the reset, and each
        column corresponds to the bitstring measured after the reset.
    """
    # establish default as all operational qubits in qc
    if qubits is None:
        qubits = qc.qubits()

    qubits = sorted(qubits)

    groups = list(itertools.combinations(qubits, joint_group_size))
    confusion_matrices = {}

    total_num_rounds = len(groups) * 2**joint_group_size
    with tqdm(total=total_num_rounds, disable=not show_progress_bar) as pbar:
        for group in groups:
            # program prepares a bit string (specified by parameterization) and immediately measures
            prep_program = _readout_group_parameterized_bitstring(group)
            prep_executable = qc.compiler.native_quil_to_executable(
                prep_program)

            matrix = np.zeros((2**joint_group_size, 2**joint_group_size))
            for row, bitstring in enumerate(
                    itertools.product([0, 1], repeat=joint_group_size)):
                for _ in range(num_trials):

                    # try preparation at most 10 times.
                    for _ in range(10):
                        # prepare the given bitstring and measure
                        result = qc.run(prep_executable,
                                        memory_map={
                                            'target':
                                            [bit * pi for bit in bitstring]
                                        })

                        # if the preparation is successful, move on to reset.
                        if np.array_equal(result[0], bitstring):
                            break

                    # execute program that measures the post-reset state
                    if use_active_reset:
                        # program runs immediately after prep program and actively resets qubits
                        reset_measure_program = Program(RESET())
                    else:
                        # this program automatically waits pre-allotted time for qubits to decay
                        reset_measure_program = Program()
                    ro = reset_measure_program.declare('ro',
                                                       memory_type='BIT',
                                                       memory_size=len(qubits))
                    for idx, qubit in enumerate(group):
                        reset_measure_program += MEASURE(qubit, ro[idx])
                    executable = qc.compiler.native_quil_to_executable(
                        reset_measure_program)
                    results = qc.run(executable)

                    # update confusion matrix
                    for result in results:
                        base = np.array(
                            [2**i for i in reversed(range(joint_group_size))])
                        observed = np.sum(base * result)
                        matrix[row, observed] += 1 / num_trials

                # update the progress bar
                pbar.update(1)

            # store completed confusion matrix in dictionary
            confusion_matrices[group] = matrix

    return confusion_matrices
 def run(qc: QuantumComputer, seq: List[Program], sg: List[Tuple], num_trials: int) -> np.ndarray:
     prog = rb_seq_to_program(seq, sg).wrap_in_numshots_loop(num_trials)
     executable = qc.compiler.native_quil_to_executable(prog)
     return qc.run(executable)
Пример #4
0
def measure_observables(qc: QuantumComputer, tomo_experiment: TomographyExperiment,
                        n_shots: int = 10000, progress_callback=None, active_reset=False,
                        symmetrize_readout: str = 'exhaustive',
                        calibrate_readout: str = 'plus-eig'):
    """
    Measure all the observables in a TomographyExperiment.

    :param qc: A QuantumComputer which can run quantum programs
    :param tomo_experiment: A suite of tomographic observables to measure
    :param n_shots: The number of shots to take per ExperimentSetting
    :param progress_callback: If not None, this function is called each time a group of
        settings is run with arguments ``f(i, len(tomo_experiment)`` such that the progress
        is ``i / len(tomo_experiment)``.
    :param active_reset: Whether to actively reset qubits instead of waiting several
        times the coherence length for qubits to decay to |0> naturally. Setting this
        to True is much faster but there is a ~1% error per qubit in the reset operation.
        Thermal noise from "traditional" reset is not routinely characterized but is of the same
        order.
    :param symmetrize_readout: Method used to symmetrize the readout errors, i.e. set
        p(0|1) = p(1|0). For uncorrelated readout errors, this can be achieved by randomly
        selecting between the POVMs {X.D1.X, X.D0.X} and {D0, D1} (where both D0 and D1 are
        diagonal). However, here we currently support exhaustive symmetrization and loop through
        all possible 2^n POVMs {X/I . POVM . X/I}^n, and obtain symmetrization more generally,
        i.e. set p(00|00) = p(01|01) = .. = p(11|11), as well as p(00|01) = p(01|00) etc. If this
        is None, no symmetrization is performed. The exhaustive method can be specified by setting
        this variable to 'exhaustive' (default value). Set to `None` if no symmetrization is
        desired.
    :param calibrate_readout: Method used to calibrate the readout results. Currently, the only
        method supported is normalizing against the operator's expectation value in its +1
        eigenstate, which can be specified by setting this variable to 'plus-eig' (default value).
        The preceding symmetrization and this step together yield a more accurate estimation of the observable. Set to `None` if no calibration is desired.
    """
    # calibration readout only works with symmetrization turned on
    if calibrate_readout is not None and symmetrize_readout is None:
        raise ValueError("Readout calibration only works with readout symmetrization turned on")

    # Outer loop over a collection of grouped settings for which we can simultaneously
    # estimate.
    for i, settings in enumerate(tomo_experiment):

        log.info(f"Collecting bitstrings for the {len(settings)} settings: {settings}")

        # 1.1 Prepare a state according to the amalgam of all setting.in_state
        total_prog = Program()
        if active_reset:
            total_prog += RESET()
        max_weight_in_state = _max_weight_state(setting.in_state for setting in settings)
        for oneq_state in max_weight_in_state.states:
            total_prog += _one_q_state_prep(oneq_state)

        # 1.2 Add in the program
        total_prog += tomo_experiment.program

        # 1.3 Measure the state according to setting.out_operator
        max_weight_out_op = _max_weight_operator(setting.out_operator for setting in settings)
        for qubit, op_str in max_weight_out_op:
            total_prog += _local_pauli_eig_meas(op_str, qubit)

        # 2. Symmetrization
        qubits = max_weight_out_op.get_qubits()

        if symmetrize_readout == 'exhaustive' and len(qubits) > 0:
            bitstrings, d_qub_idx = _exhaustive_symmetrization(qc, qubits, n_shots, total_prog)

        elif symmetrize_readout is None and len(qubits) > 0:
            total_prog_no_symm = total_prog.copy()
            ro = total_prog_no_symm.declare('ro', 'BIT', len(qubits))
            d_qub_idx = {}
            for i, q in enumerate(qubits):
                total_prog_no_symm += MEASURE(q, ro[i])
                # Keep track of qubit-classical register mapping via dict
                d_qub_idx[q] = i
            total_prog_no_symm.wrap_in_numshots_loop(n_shots)
            total_prog_no_symm_native = qc.compiler.quil_to_native_quil(total_prog_no_symm)
            total_prog_no_symm_bin = qc.compiler.native_quil_to_executable(total_prog_no_symm_native)
            bitstrings = qc.run(total_prog_no_symm_bin)

        elif len(qubits) == 0:
            # looks like an identity operation
            pass

        else:
            raise ValueError("Readout symmetrization method must be either 'exhaustive' or None")

        if progress_callback is not None:
            progress_callback(i, len(tomo_experiment))

        # 3. Post-process
        # Inner loop over the grouped settings. They only differ in which qubits' measurements
        # we include in the post-processing. For example, if `settings` is Z1, Z2, Z1Z2 and we
        # measure (n_shots, n_qubits=2) obs_strings then the full operator value involves selecting
        # either the first column, second column, or both and multiplying along the row.
        for setting in settings:
            # 3.1 Get the term's coefficient so we can multiply it in later.
            coeff = complex(setting.out_operator.coefficient)
            if not np.isclose(coeff.imag, 0):
                raise ValueError(f"{setting}'s out_operator has a complex coefficient.")
            coeff = coeff.real

            # 3.2 Special case for measuring the "identity" operator, which doesn't make much
            #     sense but should happen perfectly.
            if is_identity(setting.out_operator):
                yield ExperimentResult(
                    setting=setting,
                    expectation=coeff,
                    std_err=0.0,
                    total_counts=n_shots,
                )
                continue

            # 3.3 Obtain statistics from result of experiment
            obs_mean, obs_var = _stats_from_measurements(bitstrings, d_qub_idx, setting, n_shots, coeff)

            if calibrate_readout == 'plus-eig':
                # 4 Readout calibration
                # 4.1 Obtain calibration program
                calibr_prog = _calibration_program(qc, tomo_experiment, setting)
                # 4.2 Perform symmetrization on the calibration program
                if symmetrize_readout == 'exhaustive':
                    qubs_calibr = setting.out_operator.get_qubits()
                    calibr_shots = n_shots
                    calibr_results, d_calibr_qub_idx = _exhaustive_symmetrization(qc, qubs_calibr, calibr_shots, calibr_prog)

                else:
                    raise ValueError("Readout symmetrization method must be either 'exhaustive' or None")

                # 4.3 Obtain statistics from the measurement process
                obs_calibr_mean, obs_calibr_var = _stats_from_measurements(calibr_results, d_calibr_qub_idx, setting, calibr_shots)
                # 4.3 Calibrate the readout results
                corrected_mean = obs_mean / obs_calibr_mean
                corrected_var = ratio_variance(obs_mean, obs_var, obs_calibr_mean, obs_calibr_var)

                yield ExperimentResult(
                    setting=setting,
                    expectation=corrected_mean.item(),
                    std_err=np.sqrt(corrected_var).item(),
                    total_counts=n_shots,
                    raw_expectation=obs_mean.item(),
                    raw_std_err=np.sqrt(obs_var).item(),
                    calibration_expectation=obs_calibr_mean.item(),
                    calibration_std_err=np.sqrt(obs_calibr_var).item(),
                    calibration_counts=calibr_shots,
                )

            elif calibrate_readout is None:
                # No calibration
                yield ExperimentResult(
                    setting=setting,
                    expectation=obs_mean.item(),
                    std_err=np.sqrt(obs_var).item(),
                    total_counts=n_shots,
                )

            else:
                raise ValueError("Calibration readout method must be either 'plus-eig' or None")
Пример #5
0
 def run(qc: QuantumComputer, exp: Program, n_trials: int) -> np.ndarray:
     exp.wrap_in_numshots_loop(n_trials)
     executable = qc.compiler.native_quil_to_executable(basic_compile(exp))
     return qc.run(executable)
Пример #6
0
class ForestDiscreteEnv(gym.Env):
    """The Rigetti Forest environment.

    This implements a Gym environment for gate-based quantum computing with
    problem-specific rewards on the Rigetti hardware.

    Attributes:
        observation: A np.array, formed by concatenating observed bitstring values
                     with a vector containing the problem weights.
        observation_space: The (continuous) set of possible observations.
        action space: The space of discrete actions.
        instrs: A table mapping action IDs to PyQuil gates.

    Args:
        data: A path to a numpy dataset.
        label: Either a path to a dataset of labels, or a single label value.
        shuffle: A flag indicating whether the data should be randomly shuffled.
        qpu: A flag indicating whether to run on the qpu given by QPU_NAME.

    """
    def __init__(self, data, label, shuffle=False, qpu=False):
        weights = np.load(data)
        n_graphs = len(weights)

        # read labels from file, or as single label
        if os.path.exists(label):
            labels = np.load(label)
        else:
            labels = [label for _ in range(n_graphs)]

        if shuffle:
            self._shuffled_order = np.random.permutation(n_graphs)
            weights = weights[self._shuffled_order]
            labels = labels[self._shuffled_order]

        self.pset = AllProblems(weights, labels)
        self.num_qubits = self.pset.num_variables()

        qubits = list(range(self.num_qubits))
        angles = np.linspace(0, 2 * np.pi, NUM_ANGLES, endpoint=False)
        self.instrs = [
            CNOT(q0, q1) for q0, q1 in product(qubits, qubits) if q0 != q1
        ]
        self.instrs += [
            op(theta, q)
            for q, op, theta in product(qubits, [RX, RY, RZ], angles)
        ]
        self.action_space = gym.spaces.Discrete(len(self.instrs))

        obs_len = NUM_SHOTS * self.num_qubits + len(self.pset.problem(0))
        self.observation_space = gym.spaces.Box(np.full(obs_len, -1.0),
                                                np.full(obs_len, 1.0),
                                                dtype=np.float32)

        self.reward_threshold = 0.8

        self.qpu = qpu
        if qpu:
            self._qc = get_qc(QPU_NAME)
        else:
            self._qc = QuantumComputer(
                name="qvm",
                qam=PyQVM(n_qubits=self.num_qubits),
                device=NxDevice(nx.complete_graph(self.num_qubits)),
                compiler=MinimalPyQVMCompiler(),
            )

        self.reset()

    def reset(self, problem_id=None):
        """Reset the state of the environment.

        This clears out whatever program you may have assembled so far, and
        updates the active problem.

        Args:
            problem_id: The numeric index of the problem (relative to the problem set).
                        If None, a random problem will be chosen.
        """
        if problem_id is None:
            problem_id = np.random.randint(0, self.pset.num_problems())

        self.problem_id = problem_id
        self._prob_vec = self.pset.problem(self.problem_id)
        # the scoring function (for reward computation)
        self._prob_score = self.pset.bitstrings_score(self.problem_id)

        # we put some trivial gates on each relevant qubit, so that we can
        # always recover the problem variables from the program itself
        self.program = Program([I(q) for q in range(self.num_qubits)])
        self.current_step = 0
        self.running_episode_reward = 0

        self.bitstrings, info = self._run_program(self.program)
        return self.observation

    @property
    def observation(self):
        """Get the current observed quantum + problem state."""
        # This consists of two things:
        # - the measured bitstrings
        # - the vectorized representation of the optimization problem
        #
        # In particular, the first 10*NUM_SHOTS (i.e. 100) entries are measured
        # qubit values. The remaining entries are the weights of the problem
        # graph.
        return np.concatenate([self.bitstrings.flatten(), self._prob_vec])

    def step(self, action):
        """Advance the environment by performing the specified action."""
        # get the instruction indicated by the action
        instr = self.instrs[action]
        # extend the program
        self.program.inst(instr)
        # run and get some measured bitstrings
        self.bitstrings, info = self._run_program(self.program)
        # compute the avg score of the bitstrings
        reward = self._prob_score(self.bitstrings)
        self.running_episode_reward += reward

        info["instr"] = instr
        info["reward-nb"] = reward
        self.current_step += 1

        # are we done yet?
        done = False
        if self.current_step >= MAX_PROGRAM_LENGTH:
            done = True
        if reward >= self.reward_threshold:
            reward += MAX_PROGRAM_LENGTH - self.current_step
            done = True

        return self.observation, reward, done, info

    def _wrap_program(self, program):
        # the actions select gates. but a pyquil program needs a bit more
        # namely, declaration of classical memory for readout, and suitable
        # measurement instructions
        ro = program.declare("ro", "BIT", self.num_qubits)
        for q in range(self.num_qubits):
            program.inst(MEASURE(q, ro[q]))
        program.wrap_in_numshots_loop(NUM_SHOTS)
        return program

    def _run_program(self, program):
        program = program.copy()

        if self.qpu:
            # time to go through the compiler. whee!
            pragma = Program(
                [Pragma("INITIAL_REWIRING", ['"PARTIAL"']),
                 RESET()])
            program = pragma + program
            program = self._wrap_program(program)
            nq_program = self._qc.compiler.quil_to_native_quil(program)
            gate_count = sum(1 for instr in nq_program
                             if isinstance(instr, Gate))
            executable = self._qc.compiler.native_quil_to_executable(
                nq_program)
            results = self._qc.run(executable=executable)
        else:
            program = self._wrap_program(program)
            gate_count = len(program)
            results = self._qc.run(program)

        info = {
            "gate_count": gate_count
        }  # compiled length for qpu, uncompiled for qvm
        return results, info

    def render(self, mode="human"):
        raise NotImplementedError(
            "Rendering of this environment not currently supported.")

    def seed(self, seed):
        np.random.seed(seed)
Пример #7
0
 def run_program(
         program: Program,
         qc: QuantumComputer,
 ) -> np.ndarray:
     return qc.run(qc.compile(program)).readout_data.get('ro')
Пример #8
0
def test_basic_program(qc: QuantumComputer):
    results = qc.run(qc.compile(TEST_PROGRAM)).readout_data.get("ro")

    assert results.shape == (1000, 2)
Пример #9
0
def estimate_pauli_sum(pauli_terms: List[PauliTerm],
                       basis_transform_dict: Dict[int, str],
                       program: Program,
                       variance_bound: float,
                       quantum_resource: QuantumComputer,
                       commutation_check: bool = True) -> EstimationResult:
    """
    Estimate the mean of a sum of pauli terms to set variance.

    The sample variance is calculated by

    .. math::
        \begin{align}
        \mathrm{Var}[\hat{\langle H \rangle}] = \sum_{i, j}h_{i}h_{j}
        \mathrm{Cov}(\hat{\langle P_{i} \rangle}, \hat{\langle P_{j} \rangle})
        \end{align}

    The expectation value of each Pauli operator (term and coefficient) is
    also returned.  It can be accessed through the named-tuple field
    `pauli_expectations'.

    :param pauli_terms: list of pauli terms to measure simultaneously or a
                        PauliSum object
    :param basis_transform_dict: basis transform dictionary where the key is
                                 the qubit index and the value is the basis to
                                 rotate into. Valid basis is [I, X, Y, Z].
    :param program: program generating a state to sample from.  The program
                    is deep copied to ensure no mutation of gates or program
                    is perceived by the user.
    :param variance_bound:  Bound on the variance of the estimator for the
                            PauliSum. Remember this is the SQUARE of the
                            standard error!
    :param quantum_resource: quantum abstract machine object
    :param commutation_check: Optional flag toggling a safety check
                              ensuring all terms in `pauli_terms`
                              commute with each other
    :return: estimated expected value, expected value of each Pauli term in
             the sum, covariance matrix, variance of the estimator, and the
             number of shots taken.  The objected returned is a named tuple with
             field names as follows: expected_value, pauli_expectations,
             covariance, variance, n_shots.
             `expected_value' == coef_vec.dot(pauli_expectations)
    """
    if not isinstance(pauli_terms, (list, PauliSum)):
        raise TypeError("pauli_terms needs to be a list or a PauliSum")

    if isinstance(pauli_terms, PauliSum):
        pauli_terms = pauli_terms.terms

    # check if each term commutes with everything
    if commutation_check:
        if len(commuting_sets(sum(pauli_terms))) != 1:
            raise CommutationError("Not all terms commute in the expected way")

    program = copy.deepcopy(program)
    pauli_for_rotations = PauliTerm.from_list([
        (value, key) for key, value in basis_transform_dict.items()
    ])

    program += get_rotation_program(pauli_for_rotations)

    qubits = sorted(list(basis_transform_dict.keys()))
    for qubit in qubits:
        program.inst(MEASURE(qubit, qubit))

    coeff_vec = np.array(list(map(lambda x: x.coefficient,
                                  pauli_terms))).reshape((-1, 1))

    # upper bound on samples given by IV of arXiv:1801.03524
    num_sample_ubound = int(
        np.ceil(np.sum(np.abs(coeff_vec))**2 / variance_bound))
    results = None
    sample_variance = np.infty
    number_of_samples = 0
    while (sample_variance > variance_bound
           and number_of_samples < num_sample_ubound):
        ro = program.declare('ro', 'BIT', len(qubits))
        program += [
            MEASURE(qubit, ro_reg) for qubit, ro_reg in zip(qubits, ro)
        ]
        program.wrap_in_numshots_loop(min(10000, num_sample_ubound))
        executable = quantum_resource.compiler.native_quil_to_executable(
            program)
        tresults = quantum_resource.run(executable)
        number_of_samples += len(tresults)

        parity_results = get_parity(pauli_terms, tresults)

        # Note: easy improvement would be to update mean and variance on the fly
        # instead of storing all these results.
        if results is None:
            results = parity_results
        else:
            results = np.hstack((results, parity_results))

        # calculate the expected values....
        covariance_mat = np.cov(results)
        sample_variance = coeff_vec.T.dot(covariance_mat).dot(
            coeff_vec) / results.shape[1]

    return EstimationResult(
        expected_value=coeff_vec.T.dot(np.mean(results, axis=1)),
        pauli_expectations=np.multiply(coeff_vec.flatten(),
                                       np.mean(results, axis=1).flatten()),
        covariance=covariance_mat,
        variance=sample_variance,
        n_shots=results.shape[1])
Пример #10
0
    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}")
def get_n_bit_adder_results(qc: QuantumComputer, n_bits: int,
                            registers: Optional[Tuple[Sequence[int], Sequence[int], int,
                                                      int]] = None,
                            qubits: Optional[Sequence[int]] = None, in_x_basis: bool = False,
                            num_shots: int = 100, use_param_program: bool = False,
                            use_active_reset: bool = True, show_progress_bar: bool = False) \
        -> Sequence[Sequence[Sequence[int]]]:
    """
    Convenient wrapper for collecting the results of addition for every possible pair of n_bits
    long summands.

    :param qc: the quantum resource on which to run each addition
    :param n_bits: the number of bits of one of the summands (each summand is the same length)
    :param registers: optional explicit qubit layout of each register passed to :func:`adder`
    :param qubits: available subset of qubits of the qc on which to run the circuits.
    :param in_x_basis: if true, prepare the bitstring-representation of the numbers in the x basis
        and subsequently performs all addition logic in the x basis.
    :param num_shots: the number of times to sample the output of each addition
    :param use_param_program: whether or not to use a parameterized program for state preparation.
        Doing so should speed up overall execution on a QPU.
    :param use_active_reset: whether or not to use active reset. Doing so will speed up execution
        on a QPU.
    :param show_progress_bar: displays a progress bar via tqdm if true.
    :return: A list of n_shots many outputs for each possible summation of two n_bit long summands,
        listed in increasing numerical order where the label is the 2n bit number represented by
        num = a_bits | b_bits for the addition of a + b.
    """
    if registers is None:
        registers = get_qubit_registers_for_adder(qc, n_bits, qubits)

    reset_prog = Program()
    if use_active_reset:
        reset_prog += RESET()

    add_prog = Program()
    if use_param_program:
        dummy_num = [0 for _ in range(n_bits)]
        add_prog = adder(dummy_num,
                         dummy_num,
                         *registers,
                         in_x_basis=in_x_basis,
                         use_param_program=True)

    all_results = []
    # loop over all binary strings of length n_bits
    for bits in tqdm(all_bitstrings(2 * n_bits),
                     disable=not show_progress_bar):
        # split the binary number into two numbers
        # which are the binary numbers the user wants to add.
        # They are written from (MSB .... LSB) = (a_n, ..., a_1, a_0)
        num_a = bits[:n_bits]
        num_b = bits[n_bits:]

        if not use_param_program:
            add_prog = adder(num_a,
                             num_b,
                             *registers,
                             in_x_basis=in_x_basis,
                             use_param_program=False)

        prog = reset_prog + add_prog
        prog.wrap_in_numshots_loop(num_shots)
        nat_quil = qc.compiler.quil_to_native_quil(prog)
        exe = qc.compiler.native_quil_to_executable(nat_quil)

        # Run it on the QPU or QVM
        if use_param_program:
            results = qc.run(exe, memory_map={REG_NAME: bits})
        else:
            results = qc.run(exe)
        all_results.append(results)

    return all_results